ssr-string.spec.ts 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673
  1. // @vitest-environment node
  2. import Vue from 'vue'
  3. import VM from 'vm'
  4. import { createRenderer } from 'web/entry-server-renderer'
  5. const { renderToString } = createRenderer()
  6. describe('SSR: renderToString', () => {
  7. it('static attributes', done => {
  8. renderVmWithOptions({
  9. template: '<div id="foo" bar="123"></div>'
  10. }, result => {
  11. expect(result).toContain('<div id="foo" bar="123" data-server-rendered="true"></div>')
  12. done()
  13. })
  14. })
  15. it('unary tags', done => {
  16. renderVmWithOptions({
  17. template: '<input value="123">'
  18. }, result => {
  19. expect(result).toContain('<input value="123" data-server-rendered="true">')
  20. done()
  21. })
  22. })
  23. it('dynamic attributes', done => {
  24. renderVmWithOptions({
  25. template: '<div qux="quux" :id="foo" :bar="baz"></div>',
  26. data: {
  27. foo: 'hi',
  28. baz: 123
  29. }
  30. }, result => {
  31. expect(result).toContain('<div qux="quux" id="hi" bar="123" data-server-rendered="true"></div>')
  32. done()
  33. })
  34. })
  35. it('static class', done => {
  36. renderVmWithOptions({
  37. template: '<div class="foo bar"></div>'
  38. }, result => {
  39. expect(result).toContain('<div data-server-rendered="true" class="foo bar"></div>')
  40. done()
  41. })
  42. })
  43. it('dynamic class', done => {
  44. renderVmWithOptions({
  45. template: '<div class="foo bar" :class="[a, { qux: hasQux, quux: hasQuux }]"></div>',
  46. data: {
  47. a: 'baz',
  48. hasQux: true,
  49. hasQuux: false
  50. }
  51. }, result => {
  52. expect(result).toContain('<div data-server-rendered="true" class="foo bar baz qux"></div>')
  53. done()
  54. })
  55. })
  56. it('custom component class', done => {
  57. renderVmWithOptions({
  58. template: '<div><cmp class="cmp"></cmp></div>',
  59. components: {
  60. cmp: {
  61. render: h => h('div', 'test')
  62. }
  63. }
  64. }, result => {
  65. expect(result).toContain('<div data-server-rendered="true"><div class="cmp">test</div></div>')
  66. done()
  67. })
  68. })
  69. it('nested component class', done => {
  70. renderVmWithOptions({
  71. template: '<cmp class="outer" :class="cls"></cmp>',
  72. data: { cls: { 'success': 1 }},
  73. components: {
  74. cmp: {
  75. render: h => h('div', [h('nested', { staticClass: 'nested', 'class': { 'error': 1 }})]),
  76. components: {
  77. nested: {
  78. render: h => h('div', { staticClass: 'inner' }, 'test')
  79. }
  80. }
  81. }
  82. }
  83. }, result => {
  84. expect(result).toContain('<div data-server-rendered="true" class="outer success">' +
  85. '<div class="inner nested error">test</div>' +
  86. '</div>')
  87. done()
  88. })
  89. })
  90. it('dynamic style', done => {
  91. renderVmWithOptions({
  92. template: '<div style="background-color:black" :style="{ fontSize: fontSize + \'px\', color: color }"></div>',
  93. data: {
  94. fontSize: 14,
  95. color: 'red'
  96. }
  97. }, result => {
  98. expect(result).toContain(
  99. '<div data-server-rendered="true" style="background-color:black;font-size:14px;color:red;"></div>'
  100. )
  101. done()
  102. })
  103. })
  104. it('dynamic string style', done => {
  105. renderVmWithOptions({
  106. template: '<div :style="style"></div>',
  107. data: {
  108. style: 'color:red'
  109. }
  110. }, result => {
  111. expect(result).toContain(
  112. '<div data-server-rendered="true" style="color:red;"></div>'
  113. )
  114. done()
  115. })
  116. })
  117. it('auto-prefixed style value as array', done => {
  118. renderVmWithOptions({
  119. template: '<div :style="style"></div>',
  120. data: {
  121. style: {
  122. display: ['-webkit-box', '-ms-flexbox', 'flex']
  123. }
  124. }
  125. }, result => {
  126. expect(result).toContain(
  127. '<div data-server-rendered="true" style="display:-webkit-box;display:-ms-flexbox;display:flex;"></div>'
  128. )
  129. done()
  130. })
  131. })
  132. it('custom component style', done => {
  133. renderVmWithOptions({
  134. template: '<section><comp :style="style"></comp></section>',
  135. data: {
  136. style: 'color:red'
  137. },
  138. components: {
  139. comp: {
  140. template: '<div></div>'
  141. }
  142. }
  143. }, result => {
  144. expect(result).toContain(
  145. '<section data-server-rendered="true"><div style="color:red;"></div></section>'
  146. )
  147. done()
  148. })
  149. })
  150. it('nested custom component style', done => {
  151. renderVmWithOptions({
  152. template: '<comp style="color: blue" :style="style"></comp>',
  153. data: {
  154. style: 'color:red'
  155. },
  156. components: {
  157. comp: {
  158. template: '<nested style="text-align: left;" :style="{fontSize:\'520rem\'}"></nested>',
  159. components: {
  160. nested: {
  161. template: '<div></div>'
  162. }
  163. }
  164. }
  165. }
  166. }, result => {
  167. expect(result).toContain(
  168. '<div data-server-rendered="true" style="text-align:left;font-size:520rem;color:red;"></div>'
  169. )
  170. done()
  171. })
  172. })
  173. it('component style not passed to child', done => {
  174. renderVmWithOptions({
  175. template: '<comp :style="style"></comp>',
  176. data: {
  177. style: 'color:red'
  178. },
  179. components: {
  180. comp: {
  181. template: '<div><div></div></div>'
  182. }
  183. }
  184. }, result => {
  185. expect(result).toContain(
  186. '<div data-server-rendered="true" style="color:red;"><div></div></div>'
  187. )
  188. done()
  189. })
  190. })
  191. it('component style not passed to slot', done => {
  192. renderVmWithOptions({
  193. template: '<comp :style="style"><span style="color:black"></span></comp>',
  194. data: {
  195. style: 'color:red'
  196. },
  197. components: {
  198. comp: {
  199. template: '<div><slot></slot></div>'
  200. }
  201. }
  202. }, result => {
  203. expect(result).toContain(
  204. '<div data-server-rendered="true" style="color:red;"><span style="color:black;"></span></div>'
  205. )
  206. done()
  207. })
  208. })
  209. it('attrs merging on components', done => {
  210. const Test = {
  211. render: h => h('div', {
  212. attrs: { id: 'a' }
  213. })
  214. }
  215. renderVmWithOptions({
  216. render: h => h(Test, {
  217. attrs: { id: 'b', name: 'c' }
  218. })
  219. }, res => {
  220. expect(res).toContain(
  221. '<div id="b" data-server-rendered="true" name="c"></div>'
  222. )
  223. done()
  224. })
  225. })
  226. it('domProps merging on components', done => {
  227. const Test = {
  228. render: h => h('div', {
  229. domProps: { innerHTML: 'a' }
  230. })
  231. }
  232. renderVmWithOptions({
  233. render: h => h(Test, {
  234. domProps: { innerHTML: 'b', value: 'c' }
  235. })
  236. }, res => {
  237. expect(res).toContain(
  238. '<div data-server-rendered="true" value="c">b</div>'
  239. )
  240. done()
  241. })
  242. })
  243. it('v-show directive render', done => {
  244. renderVmWithOptions({
  245. template: '<div v-show="false"><span>inner</span></div>'
  246. }, res => {
  247. expect(res).toContain(
  248. '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'
  249. )
  250. done()
  251. })
  252. })
  253. it('v-show directive merge with style', done => {
  254. renderVmWithOptions({
  255. template: '<div :style="[{lineHeight: 1}]" v-show="false"><span>inner</span></div>'
  256. }, res => {
  257. expect(res).toContain(
  258. '<div data-server-rendered="true" style="line-height:1;display:none;"><span>inner</span></div>'
  259. )
  260. done()
  261. })
  262. })
  263. it('v-show directive not passed to child', done => {
  264. renderVmWithOptions({
  265. template: '<foo v-show="false"></foo>',
  266. components: {
  267. foo: {
  268. template: '<div><span>inner</span></div>'
  269. }
  270. }
  271. }, res => {
  272. expect(res).toContain(
  273. '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'
  274. )
  275. done()
  276. })
  277. })
  278. it('v-show directive not passed to slot', done => {
  279. renderVmWithOptions({
  280. template: '<foo v-show="false"><span>inner</span></foo>',
  281. components: {
  282. foo: {
  283. template: '<div><slot></slot></div>'
  284. }
  285. }
  286. }, res => {
  287. expect(res).toContain(
  288. '<div data-server-rendered="true" style="display:none;"><span>inner</span></div>'
  289. )
  290. done()
  291. })
  292. })
  293. it('v-show directive merging on components', done => {
  294. renderVmWithOptions({
  295. template: '<foo v-show="false"></foo>',
  296. components: {
  297. foo: {
  298. render: h => h('bar', {
  299. directives: [{
  300. name: 'show',
  301. value: true
  302. }]
  303. }),
  304. components: {
  305. bar: {
  306. render: h => h('div', 'inner')
  307. }
  308. }
  309. }
  310. }
  311. }, res => {
  312. expect(res).toContain(
  313. '<div data-server-rendered="true" style="display:none;">inner</div>'
  314. )
  315. done()
  316. })
  317. })
  318. it('text interpolation', done => {
  319. renderVmWithOptions({
  320. template: '<div>{{ foo }} side {{ bar }}</div>',
  321. data: {
  322. foo: 'server',
  323. bar: '<span>rendering</span>'
  324. }
  325. }, result => {
  326. expect(result).toContain('<div data-server-rendered="true">server side &lt;span&gt;rendering&lt;/span&gt;</div>')
  327. done()
  328. })
  329. })
  330. it('v-html on root', done => {
  331. renderVmWithOptions({
  332. template: '<div v-html="text"></div>',
  333. data: {
  334. text: '<span>foo</span>'
  335. }
  336. }, result => {
  337. expect(result).toContain('<div data-server-rendered="true"><span>foo</span></div>')
  338. done()
  339. })
  340. })
  341. it('v-text on root', done => {
  342. renderVmWithOptions({
  343. template: '<div v-text="text"></div>',
  344. data: {
  345. text: '<span>foo</span>'
  346. }
  347. }, result => {
  348. expect(result).toContain('<div data-server-rendered="true">&lt;span&gt;foo&lt;/span&gt;</div>')
  349. done()
  350. })
  351. })
  352. it('v-html', done => {
  353. renderVmWithOptions({
  354. template: '<div><div v-html="text"></div></div>',
  355. data: {
  356. text: '<span>foo</span>'
  357. }
  358. }, result => {
  359. expect(result).toContain('<div data-server-rendered="true"><div><span>foo</span></div></div>')
  360. done()
  361. })
  362. })
  363. it('v-html with null value', done => {
  364. renderVmWithOptions({
  365. template: '<div><div v-html="text"></div></div>',
  366. data: {
  367. text: null
  368. }
  369. }, result => {
  370. expect(result).toContain('<div data-server-rendered="true"><div></div></div>')
  371. done()
  372. })
  373. })
  374. it('v-text', done => {
  375. renderVmWithOptions({
  376. template: '<div><div v-text="text"></div></div>',
  377. data: {
  378. text: '<span>foo</span>'
  379. }
  380. }, result => {
  381. expect(result).toContain('<div data-server-rendered="true"><div>&lt;span&gt;foo&lt;/span&gt;</div></div>')
  382. done()
  383. })
  384. })
  385. it('v-text with null value', done => {
  386. renderVmWithOptions({
  387. template: '<div><div v-text="text"></div></div>',
  388. data: {
  389. text: null
  390. }
  391. }, result => {
  392. expect(result).toContain('<div data-server-rendered="true"><div></div></div>')
  393. done()
  394. })
  395. })
  396. it('child component (hoc)', done => {
  397. renderVmWithOptions({
  398. template: '<child class="foo" :msg="msg"></child>',
  399. data: {
  400. msg: 'hello'
  401. },
  402. components: {
  403. child: {
  404. props: ['msg'],
  405. data () {
  406. return { name: 'bar' }
  407. },
  408. render () {
  409. const h = this.$createElement
  410. return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`])
  411. }
  412. }
  413. }
  414. }, result => {
  415. expect(result).toContain('<div data-server-rendered="true" class="foo bar">hello bar</div>')
  416. done()
  417. })
  418. })
  419. it('has correct lifecycle during render', done => {
  420. let lifecycleCount = 1
  421. renderVmWithOptions({
  422. template: '<div><span>{{ val }}</span><test></test></div>',
  423. data: {
  424. val: 'hi'
  425. },
  426. beforeCreate () {
  427. expect(lifecycleCount++).toBe(1)
  428. },
  429. created () {
  430. this.val = 'hello'
  431. expect(this.val).toBe('hello')
  432. expect(lifecycleCount++).toBe(2)
  433. },
  434. components: {
  435. test: {
  436. beforeCreate () {
  437. expect(lifecycleCount++).toBe(3)
  438. },
  439. created () {
  440. expect(lifecycleCount++).toBe(4)
  441. },
  442. render () {
  443. expect(lifecycleCount++).toBeGreaterThan(4)
  444. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  445. }
  446. }
  447. }
  448. }, result => {
  449. expect(result).toContain(
  450. '<div data-server-rendered="true">' +
  451. '<span>hello</span>' +
  452. '<span class="b">testAsync</span>' +
  453. '</div>'
  454. )
  455. done()
  456. })
  457. })
  458. it('computed properties', done => {
  459. renderVmWithOptions({
  460. template: '<div>{{ b }}</div>',
  461. data: {
  462. a: {
  463. b: 1
  464. }
  465. },
  466. computed: {
  467. b () {
  468. return this.a.b + 1
  469. }
  470. },
  471. created () {
  472. this.a.b = 2
  473. expect(this.b).toBe(3)
  474. }
  475. }, result => {
  476. expect(result).toContain('<div data-server-rendered="true">3</div>')
  477. done()
  478. })
  479. })
  480. it('renders async component', done => {
  481. renderVmWithOptions({
  482. template: `
  483. <div>
  484. <test-async></test-async>
  485. </div>
  486. `,
  487. components: {
  488. testAsync (resolve) {
  489. setTimeout(() => resolve({
  490. render () {
  491. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  492. }
  493. }), 1)
  494. }
  495. }
  496. }, result => {
  497. expect(result).toContain('<div data-server-rendered="true"><span class="b">testAsync</span></div>')
  498. done()
  499. })
  500. })
  501. it('renders async component (Promise, nested)', done => {
  502. const Foo = () => Promise.resolve({
  503. render: h => h('div', [h('span', 'foo'), h(Bar)])
  504. })
  505. const Bar = () => ({
  506. component: Promise.resolve({
  507. render: h => h('span', 'bar')
  508. })
  509. })
  510. renderVmWithOptions({
  511. render: h => h(Foo)
  512. }, res => {
  513. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`)
  514. done()
  515. })
  516. })
  517. it('renders async component (ES module)', done => {
  518. const Foo = () => Promise.resolve({
  519. __esModule: true,
  520. default: {
  521. render: h => h('div', [h('span', 'foo'), h(Bar)])
  522. }
  523. })
  524. const Bar = () => ({
  525. component: Promise.resolve({
  526. __esModule: true,
  527. default: {
  528. render: h => h('span', 'bar')
  529. }
  530. })
  531. })
  532. renderVmWithOptions({
  533. render: h => h(Foo)
  534. }, res => {
  535. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span><span>bar</span></div>`)
  536. done()
  537. })
  538. })
  539. it('renders async component (hoc)', done => {
  540. renderVmWithOptions({
  541. template: '<test-async></test-async>',
  542. components: {
  543. testAsync: () => Promise.resolve({
  544. render () {
  545. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  546. }
  547. })
  548. }
  549. }, result => {
  550. expect(result).toContain('<span data-server-rendered="true" class="b">testAsync</span>')
  551. done()
  552. })
  553. })
  554. it('renders async component (functional, single node)', done => {
  555. renderVmWithOptions({
  556. template: `
  557. <div>
  558. <test-async></test-async>
  559. </div>
  560. `,
  561. components: {
  562. testAsync (resolve) {
  563. setTimeout(() => resolve({
  564. functional: true,
  565. render (h) {
  566. return h('span', { class: ['b'] }, 'testAsync')
  567. }
  568. }), 1)
  569. }
  570. }
  571. }, result => {
  572. expect(result).toContain('<div data-server-rendered="true"><span class="b">testAsync</span></div>')
  573. done()
  574. })
  575. })
  576. it('renders async component (functional, multiple nodes)', done => {
  577. renderVmWithOptions({
  578. template: `
  579. <div>
  580. <test-async></test-async>
  581. </div>
  582. `,
  583. components: {
  584. testAsync (resolve) {
  585. setTimeout(() => resolve({
  586. functional: true,
  587. render (h) {
  588. return [
  589. h('span', { class: ['a'] }, 'foo'),
  590. h('span', { class: ['b'] }, 'bar')
  591. ]
  592. }
  593. }), 1)
  594. }
  595. }
  596. }, result => {
  597. expect(result).toContain(
  598. '<div data-server-rendered="true">' +
  599. '<span class="a">foo</span>' +
  600. '<span class="b">bar</span>' +
  601. '</div>'
  602. )
  603. done()
  604. })
  605. })
  606. it('renders nested async functional component', done => {
  607. renderVmWithOptions({
  608. template: `
  609. <div>
  610. <outer-async></outer-async>
  611. </div>
  612. `,
  613. components: {
  614. outerAsync (resolve) {
  615. setTimeout(() => resolve({
  616. functional: true,
  617. render (h) {
  618. return h('innerAsync')
  619. }
  620. }), 1)
  621. },
  622. innerAsync (resolve) {
  623. setTimeout(() => resolve({
  624. functional: true,
  625. render (h) {
  626. return h('span', { class: ['a'] }, 'inner')
  627. },
  628. }), 1)
  629. }
  630. }
  631. }, result => {
  632. expect(result).toContain(
  633. '<div data-server-rendered="true">' +
  634. '<span class="a">inner</span>' +
  635. '</div>'
  636. )
  637. done()
  638. })
  639. })
  640. it('should catch async component error', done => {
  641. renderToString(new Vue({
  642. template: '<test-async></test-async>',
  643. components: {
  644. testAsync: () => Promise.resolve({
  645. render () {
  646. throw new Error('foo')
  647. }
  648. })
  649. }
  650. }), (err, result) => {
  651. expect(err).toBeTruthy()
  652. expect(result).toBeUndefined()
  653. expect('foo').toHaveBeenWarned()
  654. done()
  655. })
  656. })
  657. // #11963, #10391
  658. it('renders async children passed in slots', done => {
  659. const Parent = {
  660. template: `<div><slot name="child"/></div>`
  661. }
  662. const Child = {
  663. template: `<p>child</p>`
  664. }
  665. renderVmWithOptions({
  666. template: `
  667. <Parent>
  668. <template #child>
  669. <Child/>
  670. </template>
  671. </Parent>
  672. `,
  673. components: {
  674. Parent,
  675. Child: () => Promise.resolve(Child)
  676. }
  677. }, result => {
  678. expect(result).toContain(
  679. `<div data-server-rendered="true"><p>child</p></div>`
  680. )
  681. done()
  682. })
  683. })
  684. it('everything together', done => {
  685. renderVmWithOptions({
  686. template: `
  687. <div>
  688. <p class="hi">yoyo</p>
  689. <div id="ho" :class="{ red: isRed }"></div>
  690. <span>{{ test }}</span>
  691. <input :value="test">
  692. <img :src="imageUrl">
  693. <test></test>
  694. <test-async></test-async>
  695. </div>
  696. `,
  697. data: {
  698. test: 'hi',
  699. isRed: true,
  700. imageUrl: 'https://vuejs.org/images/logo.png'
  701. },
  702. components: {
  703. test: {
  704. render () {
  705. return this.$createElement('div', { class: ['a'] }, 'test')
  706. }
  707. },
  708. testAsync (resolve) {
  709. resolve({
  710. render () {
  711. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  712. }
  713. })
  714. }
  715. }
  716. }, result => {
  717. expect(result).toContain(
  718. '<div data-server-rendered="true">' +
  719. '<p class="hi">yoyo</p> ' +
  720. '<div id="ho" class="red"></div> ' +
  721. '<span>hi</span> ' +
  722. '<input value="hi"> ' +
  723. '<img src="https://vuejs.org/images/logo.png"> ' +
  724. '<div class="a">test</div> ' +
  725. '<span class="b">testAsync</span>' +
  726. '</div>'
  727. )
  728. done()
  729. })
  730. })
  731. it('normal attr', done => {
  732. renderVmWithOptions({
  733. template: `
  734. <div>
  735. <span :test="'ok'">hello</span>
  736. <span :test="null">hello</span>
  737. <span :test="false">hello</span>
  738. <span :test="true">hello</span>
  739. <span :test="0">hello</span>
  740. </div>
  741. `
  742. }, result => {
  743. expect(result).toContain(
  744. '<div data-server-rendered="true">' +
  745. '<span test="ok">hello</span> ' +
  746. '<span>hello</span> ' +
  747. '<span>hello</span> ' +
  748. '<span test="true">hello</span> ' +
  749. '<span test="0">hello</span>' +
  750. '</div>'
  751. )
  752. done()
  753. })
  754. })
  755. it('enumerated attr', done => {
  756. renderVmWithOptions({
  757. template: `
  758. <div>
  759. <span :draggable="true">hello</span>
  760. <span :draggable="'ok'">hello</span>
  761. <span :draggable="null">hello</span>
  762. <span :draggable="false">hello</span>
  763. <span :draggable="''">hello</span>
  764. <span :draggable="'false'">hello</span>
  765. </div>
  766. `
  767. }, result => {
  768. expect(result).toContain(
  769. '<div data-server-rendered="true">' +
  770. '<span draggable="true">hello</span> ' +
  771. '<span draggable="true">hello</span> ' +
  772. '<span draggable="false">hello</span> ' +
  773. '<span draggable="false">hello</span> ' +
  774. '<span draggable="true">hello</span> ' +
  775. '<span draggable="false">hello</span>' +
  776. '</div>'
  777. )
  778. done()
  779. })
  780. })
  781. it('boolean attr', done => {
  782. renderVmWithOptions({
  783. template: `
  784. <div>
  785. <span :disabled="true">hello</span>
  786. <span :disabled="'ok'">hello</span>
  787. <span :disabled="null">hello</span>
  788. <span :disabled="''">hello</span>
  789. </div>
  790. `
  791. }, result => {
  792. expect(result).toContain(
  793. '<div data-server-rendered="true">' +
  794. '<span disabled="disabled">hello</span> ' +
  795. '<span disabled="disabled">hello</span> ' +
  796. '<span>hello</span> ' +
  797. '<span disabled="disabled">hello</span>' +
  798. '</div>'
  799. )
  800. done()
  801. })
  802. })
  803. it('v-bind object', done => {
  804. renderVmWithOptions({
  805. data: {
  806. test: { id: 'a', class: ['a', 'b'], value: 'c' }
  807. },
  808. template: '<input v-bind="test">'
  809. }, result => {
  810. expect(result).toContain('<input id="a" data-server-rendered="true" value="c" class="a b">')
  811. done()
  812. })
  813. })
  814. it('custom directives', done => {
  815. const renderer = createRenderer({
  816. directives: {
  817. 'class-prefixer': (node, dir) => {
  818. if (node.data.class) {
  819. node.data.class = `${dir.value}-${node.data.class}`
  820. }
  821. if (node.data.staticClass) {
  822. node.data.staticClass = `${dir.value}-${node.data.staticClass}`
  823. }
  824. }
  825. }
  826. })
  827. renderer.renderToString(new Vue({
  828. render () {
  829. const h = this.$createElement
  830. return h('p', {
  831. class: 'class1',
  832. staticClass: 'class2',
  833. directives: [{
  834. name: 'class-prefixer',
  835. value: 'my'
  836. }]
  837. }, ['hello world'])
  838. }
  839. }), (err, result) => {
  840. expect(err).toBeNull()
  841. expect(result).toContain('<p data-server-rendered="true" class="my-class2 my-class1">hello world</p>')
  842. done()
  843. })
  844. })
  845. it('should not warn for custom directives that do not have server-side implementation', done => {
  846. renderToString(new Vue({
  847. directives: {
  848. test: {
  849. bind() {
  850. // noop
  851. }
  852. }
  853. },
  854. template: '<div v-test></div>',
  855. }), () => {
  856. expect('Failed to resolve directive: test').not.toHaveBeenWarned()
  857. done()
  858. })
  859. })
  860. it('_scopeId', done => {
  861. renderVmWithOptions({
  862. _scopeId: '_v-parent',
  863. template: '<div id="foo"><p><child></child></p></div>',
  864. components: {
  865. child: {
  866. _scopeId: '_v-child',
  867. render () {
  868. const h = this.$createElement
  869. return h('div', null, [h('span', null, ['foo'])])
  870. }
  871. }
  872. }
  873. }, result => {
  874. expect(result).toContain(
  875. '<div id="foo" data-server-rendered="true" _v-parent>' +
  876. '<p _v-parent>' +
  877. '<div _v-child _v-parent><span _v-child>foo</span></div>' +
  878. '</p>' +
  879. '</div>'
  880. )
  881. done()
  882. })
  883. })
  884. it('_scopeId on slot content', done => {
  885. renderVmWithOptions({
  886. _scopeId: '_v-parent',
  887. template: '<div><child><p>foo</p></child></div>',
  888. components: {
  889. child: {
  890. _scopeId: '_v-child',
  891. render () {
  892. const h = this.$createElement
  893. return h('div', null, this.$slots.default)
  894. }
  895. }
  896. }
  897. }, result => {
  898. expect(result).toContain(
  899. '<div data-server-rendered="true" _v-parent>' +
  900. '<div _v-child _v-parent><p _v-child _v-parent>foo</p></div>' +
  901. '</div>'
  902. )
  903. done()
  904. })
  905. })
  906. it('comment nodes', done => {
  907. renderVmWithOptions({
  908. template: '<div><transition><div v-if="false"></div></transition></div>'
  909. }, result => {
  910. expect(result).toContain(`<div data-server-rendered="true"><!----></div>`)
  911. done()
  912. })
  913. })
  914. it('should catch error', done => {
  915. renderToString(new Vue({
  916. render () {
  917. throw new Error('oops')
  918. }
  919. }), err => {
  920. expect(err instanceof Error).toBe(true)
  921. expect(`oops`).toHaveBeenWarned()
  922. done()
  923. })
  924. })
  925. it('default value Foreign Function', () => {
  926. const FunctionConstructor = VM.runInNewContext('Function')
  927. const func = () => 123
  928. const vm = new Vue({
  929. props: {
  930. a: {
  931. type: FunctionConstructor,
  932. default: func
  933. }
  934. },
  935. propsData: {
  936. a: undefined
  937. }
  938. })
  939. expect(vm.a).toBe(func)
  940. })
  941. it('should prevent xss in attributes', done => {
  942. renderVmWithOptions({
  943. data: {
  944. xss: '"><script>alert(1)</script>'
  945. },
  946. template: `
  947. <div>
  948. <a :title="xss" :style="{ color: xss }" :class="[xss]">foo</a>
  949. </div>
  950. `
  951. }, res => {
  952. expect(res).not.toContain(`<script>alert(1)</script>`)
  953. done()
  954. })
  955. })
  956. it('should prevent xss in attribute names', done => {
  957. renderVmWithOptions({
  958. data: {
  959. xss: {
  960. 'foo="bar"></div><script>alert(1)</script>': ''
  961. }
  962. },
  963. template: `
  964. <div v-bind="xss"></div>
  965. `
  966. }, res => {
  967. expect(res).not.toContain(`<script>alert(1)</script>`)
  968. done()
  969. })
  970. })
  971. it('should prevent xss in attribute names (optimized)', done => {
  972. renderVmWithOptions({
  973. data: {
  974. xss: {
  975. 'foo="bar"></div><script>alert(1)</script>': ''
  976. }
  977. },
  978. template: `
  979. <div>
  980. <a v-bind="xss">foo</a>
  981. </div>
  982. `
  983. }, res => {
  984. expect(res).not.toContain(`<script>alert(1)</script>`)
  985. done()
  986. })
  987. })
  988. it('should prevent script xss with v-bind object syntax + array value', done => {
  989. renderVmWithOptions({
  990. data: {
  991. test: ['"><script>alert(1)</script><!--"']
  992. },
  993. template: `<div v-bind="{ test }"></div>`
  994. }, res => {
  995. expect(res).not.toContain(`<script>alert(1)</script>`)
  996. done()
  997. })
  998. })
  999. it('v-if', done => {
  1000. renderVmWithOptions({
  1001. template: `
  1002. <div>
  1003. <span v-if="true">foo</span>
  1004. <span v-if="false">bar</span>
  1005. </div>
  1006. `
  1007. }, res => {
  1008. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span> <!----></div>`)
  1009. done()
  1010. })
  1011. })
  1012. it('v-for', done => {
  1013. renderVmWithOptions({
  1014. template: `
  1015. <div>
  1016. <span>foo</span>
  1017. <span v-for="i in 2">{{ i }}</span>
  1018. </div>
  1019. `
  1020. }, res => {
  1021. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span> <span>1</span><span>2</span></div>`)
  1022. done()
  1023. })
  1024. })
  1025. it('template v-if', done => {
  1026. renderVmWithOptions({
  1027. template: `
  1028. <div>
  1029. <span>foo</span>
  1030. <template v-if="true">
  1031. <span>foo</span> bar <span>baz</span>
  1032. </template>
  1033. </div>
  1034. `
  1035. }, res => {
  1036. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span> <span>foo</span> bar <span>baz</span></div>`)
  1037. done()
  1038. })
  1039. })
  1040. it('template v-for', done => {
  1041. renderVmWithOptions({
  1042. template: `
  1043. <div>
  1044. <span>foo</span>
  1045. <template v-for="i in 2">
  1046. <span>{{ i }}</span><span>bar</span>
  1047. </template>
  1048. </div>
  1049. `
  1050. }, res => {
  1051. expect(res).toContain(`<div data-server-rendered="true"><span>foo</span> <span>1</span><span>bar</span><span>2</span><span>bar</span></div>`)
  1052. done()
  1053. })
  1054. })
  1055. it('with inheritAttrs: false + $attrs', done => {
  1056. renderVmWithOptions({
  1057. template: `<foo id="a"/>`,
  1058. components: {
  1059. foo: {
  1060. inheritAttrs: false,
  1061. template: `<div><div v-bind="$attrs"></div></div>`
  1062. }
  1063. }
  1064. }, res => {
  1065. expect(res).toBe(`<div data-server-rendered="true"><div id="a"></div></div>`)
  1066. done()
  1067. })
  1068. })
  1069. it('should escape static strings', done => {
  1070. renderVmWithOptions({
  1071. template: `<div>&lt;foo&gt;</div>`
  1072. }, res => {
  1073. expect(res).toBe(`<div data-server-rendered="true">&lt;foo&gt;</div>`)
  1074. done()
  1075. })
  1076. })
  1077. it('should not cache computed properties', done => {
  1078. renderVmWithOptions({
  1079. template: `<div>{{ foo }}</div>`,
  1080. data: () => ({ bar: 1 }),
  1081. computed: {
  1082. foo () { return this.bar + 1 }
  1083. },
  1084. created () {
  1085. this.foo // access
  1086. this.bar++ // trigger change
  1087. }
  1088. }, res => {
  1089. expect(res).toBe(`<div data-server-rendered="true">3</div>`)
  1090. done()
  1091. })
  1092. })
  1093. // #8977
  1094. it('should call computed properties with vm as first argument', done => {
  1095. renderToString(new Vue({
  1096. data: {
  1097. firstName: 'Evan',
  1098. lastName: 'You'
  1099. },
  1100. computed: {
  1101. fullName: ({ firstName, lastName }) => `${firstName} ${lastName}`,
  1102. },
  1103. template: '<div>{{ fullName }}</div>',
  1104. }), (err, result) => {
  1105. expect(err).toBeNull()
  1106. expect(result).toContain('<div data-server-rendered="true">Evan You</div>')
  1107. done()
  1108. })
  1109. })
  1110. it('return Promise', async () => {
  1111. await renderToString(new Vue({
  1112. template: `<div>{{ foo }}</div>`,
  1113. data: { foo: 'bar' }
  1114. }))!.then(res => {
  1115. expect(res).toBe(`<div data-server-rendered="true">bar</div>`)
  1116. })
  1117. })
  1118. it('return Promise (error)', async () => {
  1119. await renderToString(new Vue({
  1120. render () {
  1121. throw new Error('foobar')
  1122. }
  1123. }))!.catch(err => {
  1124. expect('foobar').toHaveBeenWarned()
  1125. expect(err.toString()).toContain(`foobar`)
  1126. })
  1127. })
  1128. it('should catch template compilation error', done => {
  1129. renderToString(new Vue({
  1130. template: `<div></div><div></div>`
  1131. }), (err) => {
  1132. expect(err.toString()).toContain('Component template should contain exactly one root element')
  1133. done()
  1134. })
  1135. })
  1136. // #6907
  1137. it('should not optimize root if conditions', done => {
  1138. renderVmWithOptions({
  1139. data: { foo: 123 },
  1140. template: `<input :type="'text'" v-model="foo">`
  1141. }, res => {
  1142. expect(res).toBe(`<input type="text" data-server-rendered="true" value="123">`)
  1143. done()
  1144. })
  1145. })
  1146. it('render muted properly', done => {
  1147. renderVmWithOptions({
  1148. template: '<video muted></video>'
  1149. }, result => {
  1150. expect(result).toContain('<video muted="muted" data-server-rendered="true"></video>')
  1151. done()
  1152. })
  1153. })
  1154. it('render v-model with textarea', done => {
  1155. renderVmWithOptions({
  1156. data: { foo: 'bar' },
  1157. template: '<div><textarea v-model="foo"></textarea></div>'
  1158. }, result => {
  1159. expect(result).toContain('<textarea>bar</textarea>')
  1160. done()
  1161. })
  1162. })
  1163. it('render v-model with textarea (non-optimized)', done => {
  1164. renderVmWithOptions({
  1165. render (h) {
  1166. return h('textarea', {
  1167. domProps: {
  1168. value: 'foo'
  1169. }
  1170. })
  1171. }
  1172. }, result => {
  1173. expect(result).toContain('<textarea data-server-rendered="true">foo</textarea>')
  1174. done()
  1175. })
  1176. })
  1177. it('render v-model with <select> (value binding)', done => {
  1178. renderVmWithOptions({
  1179. data: {
  1180. selected: 2,
  1181. options: [
  1182. { id: 1, label: 'one' },
  1183. { id: 2, label: 'two' }
  1184. ]
  1185. },
  1186. template: `
  1187. <div>
  1188. <select v-model="selected">
  1189. <option v-for="o in options" :value="o.id">{{ o.label }}</option>
  1190. </select>
  1191. </div>
  1192. `
  1193. }, result => {
  1194. expect(result).toContain(
  1195. '<select>' +
  1196. '<option value="1">one</option>' +
  1197. '<option selected="selected" value="2">two</option>' +
  1198. '</select>'
  1199. )
  1200. done()
  1201. })
  1202. })
  1203. it('render v-model with <select> (static value)', done => {
  1204. renderVmWithOptions({
  1205. data: {
  1206. selected: 2
  1207. },
  1208. template: `
  1209. <div>
  1210. <select v-model="selected">
  1211. <option value="1">one</option>
  1212. <option value="2">two</option>
  1213. </select>
  1214. </div>
  1215. `
  1216. }, result => {
  1217. expect(result).toContain(
  1218. '<select>' +
  1219. '<option value="1">one</option> ' +
  1220. '<option value="2" selected="selected">two</option>' +
  1221. '</select>'
  1222. )
  1223. done()
  1224. })
  1225. })
  1226. it('render v-model with <select> (text as value)', done => {
  1227. renderVmWithOptions({
  1228. data: {
  1229. selected: 2,
  1230. options: [
  1231. { id: 1, label: 'one' },
  1232. { id: 2, label: 'two' }
  1233. ]
  1234. },
  1235. template: `
  1236. <div>
  1237. <select v-model="selected">
  1238. <option v-for="o in options">{{ o.id }}</option>
  1239. </select>
  1240. </div>
  1241. `
  1242. }, result => {
  1243. expect(result).toContain(
  1244. '<select>' +
  1245. '<option>1</option>' +
  1246. '<option selected="selected">2</option>' +
  1247. '</select>'
  1248. )
  1249. done()
  1250. })
  1251. })
  1252. // #7223
  1253. it('should not double escape attribute values', done => {
  1254. renderVmWithOptions({
  1255. template: `
  1256. <div>
  1257. <div id="a\nb"></div>
  1258. </div>
  1259. `
  1260. }, result => {
  1261. expect(result).toContain(`<div id="a\nb"></div>`)
  1262. done()
  1263. })
  1264. })
  1265. // #7859
  1266. it('should not double escape class values', done => {
  1267. renderVmWithOptions({
  1268. template: `
  1269. <div>
  1270. <div class="a\nb"></div>
  1271. </div>
  1272. `
  1273. }, result => {
  1274. expect(result).toContain(`<div class="a b"></div>`)
  1275. done()
  1276. })
  1277. })
  1278. it('should expose ssr helpers on functional context', done => {
  1279. let called = false
  1280. renderVmWithOptions({
  1281. template: `<div><foo/></div>`,
  1282. components: {
  1283. foo: {
  1284. functional: true,
  1285. render (h, ctx) {
  1286. expect(ctx._ssrNode).toBeTruthy()
  1287. called = true
  1288. }
  1289. }
  1290. }
  1291. }, () => {
  1292. expect(called).toBe(true)
  1293. done()
  1294. })
  1295. })
  1296. it('should support serverPrefetch option', done => {
  1297. renderVmWithOptions({
  1298. template: `
  1299. <div>{{ count }}</div>
  1300. `,
  1301. data: {
  1302. count: 0
  1303. },
  1304. serverPrefetch () {
  1305. return new Promise<void>((resolve) => {
  1306. setTimeout(() => {
  1307. this.count = 42
  1308. resolve()
  1309. }, 1)
  1310. })
  1311. }
  1312. }, result => {
  1313. expect(result).toContain('<div data-server-rendered="true">42</div>')
  1314. done()
  1315. })
  1316. })
  1317. it('should support serverPrefetch option (nested)', done => {
  1318. renderVmWithOptions({
  1319. template: `
  1320. <div>
  1321. <span>{{ count }}</span>
  1322. <nested-prefetch></nested-prefetch>
  1323. </div>
  1324. `,
  1325. data: {
  1326. count: 0
  1327. },
  1328. serverPrefetch () {
  1329. return new Promise<void>((resolve) => {
  1330. setTimeout(() => {
  1331. this.count = 42
  1332. resolve()
  1333. }, 1)
  1334. })
  1335. },
  1336. components: {
  1337. nestedPrefetch: {
  1338. template: `
  1339. <div>{{ message }}</div>
  1340. `,
  1341. data () {
  1342. return {
  1343. message: ''
  1344. }
  1345. },
  1346. serverPrefetch () {
  1347. return new Promise<void>((resolve) => {
  1348. setTimeout(() => {
  1349. this.message = 'vue.js'
  1350. resolve()
  1351. }, 1)
  1352. })
  1353. }
  1354. }
  1355. }
  1356. }, result => {
  1357. expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
  1358. done()
  1359. })
  1360. })
  1361. it('should support serverPrefetch option (nested async)', done => {
  1362. renderVmWithOptions({
  1363. template: `
  1364. <div>
  1365. <span>{{ count }}</span>
  1366. <nested-prefetch></nested-prefetch>
  1367. </div>
  1368. `,
  1369. data: {
  1370. count: 0
  1371. },
  1372. serverPrefetch () {
  1373. return new Promise<void>((resolve) => {
  1374. setTimeout(() => {
  1375. this.count = 42
  1376. resolve()
  1377. }, 1)
  1378. })
  1379. },
  1380. components: {
  1381. nestedPrefetch (resolve) {
  1382. resolve({
  1383. template: `
  1384. <div>{{ message }}</div>
  1385. `,
  1386. data () {
  1387. return {
  1388. message: ''
  1389. }
  1390. },
  1391. serverPrefetch () {
  1392. return new Promise<void>((resolve) => {
  1393. setTimeout(() => {
  1394. this.message = 'vue.js'
  1395. resolve()
  1396. }, 1)
  1397. })
  1398. }
  1399. })
  1400. }
  1401. }
  1402. }, result => {
  1403. expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
  1404. done()
  1405. })
  1406. })
  1407. it('should merge serverPrefetch option', done => {
  1408. const mixin = {
  1409. data: {
  1410. message: ''
  1411. },
  1412. serverPrefetch () {
  1413. return new Promise<void>((resolve) => {
  1414. setTimeout(() => {
  1415. this.message = 'vue.js'
  1416. resolve()
  1417. }, 1)
  1418. })
  1419. }
  1420. }
  1421. renderVmWithOptions({
  1422. mixins: [mixin],
  1423. template: `
  1424. <div>
  1425. <span>{{ count }}</span>
  1426. <div>{{ message }}</div>
  1427. </div>
  1428. `,
  1429. data: {
  1430. count: 0
  1431. },
  1432. serverPrefetch () {
  1433. return new Promise<void>((resolve) => {
  1434. setTimeout(() => {
  1435. this.count = 42
  1436. resolve()
  1437. }, 1)
  1438. })
  1439. }
  1440. }, result => {
  1441. expect(result).toContain('<div data-server-rendered="true"><span>42</span> <div>vue.js</div></div>')
  1442. done()
  1443. })
  1444. })
  1445. it(`should skip serverPrefetch option that doesn't return a promise`, done => {
  1446. renderVmWithOptions({
  1447. template: `
  1448. <div>{{ count }}</div>
  1449. `,
  1450. data: {
  1451. count: 0
  1452. },
  1453. serverPrefetch () {
  1454. setTimeout(() => {
  1455. this.count = 42
  1456. }, 1)
  1457. }
  1458. }, result => {
  1459. expect(result).toContain('<div data-server-rendered="true">0</div>')
  1460. done()
  1461. })
  1462. })
  1463. it('should call context.rendered', done => {
  1464. let a = 0
  1465. renderToString(new Vue({
  1466. template: '<div>Hello</div>'
  1467. }), {
  1468. rendered: () => {
  1469. a = 42
  1470. }
  1471. }, (err, res) => {
  1472. expect(err).toBeNull()
  1473. expect(res).toContain('<div data-server-rendered="true">Hello</div>')
  1474. expect(a).toBe(42)
  1475. done()
  1476. })
  1477. })
  1478. it('invalid style value', done => {
  1479. renderVmWithOptions({
  1480. template: '<div :style="style"><p :style="style2"/></div>',
  1481. data: {
  1482. // all invalid, should not even have "style" attribute
  1483. style: {
  1484. opacity: {},
  1485. color: null
  1486. },
  1487. // mix of valid and invalid
  1488. style2: {
  1489. opacity: 0,
  1490. color: null
  1491. }
  1492. }
  1493. }, result => {
  1494. expect(result).toContain(
  1495. '<div data-server-rendered="true"><p style="opacity:0;"></p></div>'
  1496. )
  1497. done()
  1498. })
  1499. })
  1500. it('numeric style value', done => {
  1501. renderVmWithOptions({
  1502. template: '<div :style="style"></div>',
  1503. data: {
  1504. style: {
  1505. opacity: 0, // valid, opacity is unit-less
  1506. top: 0, // valid, top requires unit but 0 is allowed
  1507. left: 10, // invalid, left requires a unit
  1508. marginTop: '10px' // valid
  1509. }
  1510. }
  1511. }, result => {
  1512. expect(result).toContain(
  1513. '<div data-server-rendered="true" style="opacity:0;top:0;margin-top:10px;"></div>'
  1514. )
  1515. done()
  1516. })
  1517. })
  1518. it('handling max stack size limit', done => {
  1519. const vueInstance = new Vue({
  1520. template: `<div class="root">
  1521. <child v-for="(x, i) in items" :key="i"></child>
  1522. </div>`,
  1523. components: {
  1524. child: {
  1525. template: '<div class="child"><span class="child">hi</span></div>'
  1526. }
  1527. },
  1528. data: {
  1529. items: Array(1000).fill(0)
  1530. }
  1531. })
  1532. renderToString(vueInstance, err => done(err))
  1533. })
  1534. it('undefined v-model with textarea', done => {
  1535. renderVmWithOptions({
  1536. render (h) {
  1537. return h('div', [
  1538. h('textarea', {
  1539. domProps: {
  1540. value: null
  1541. }
  1542. })
  1543. ])
  1544. }
  1545. }, result => {
  1546. expect(result).toContain(
  1547. '<div data-server-rendered="true"><textarea></textarea></div>'
  1548. )
  1549. done()
  1550. })
  1551. })
  1552. it('Options inheritAttrs in parent component', done => {
  1553. const childComponent = {
  1554. template: `<div>{{ someProp }}</div>`,
  1555. props: {
  1556. someProp: {}
  1557. },
  1558. }
  1559. const parentComponent = {
  1560. template: `<childComponent v-bind="$attrs" />`,
  1561. components: { childComponent },
  1562. inheritAttrs: false
  1563. }
  1564. renderVmWithOptions({
  1565. template: `
  1566. <div>
  1567. <parentComponent some-prop="some-val" />
  1568. </div>
  1569. `,
  1570. components: { parentComponent }
  1571. }, result => {
  1572. expect(result).toContain('<div data-server-rendered="true"><div>some-val</div></div>')
  1573. done()
  1574. })
  1575. })
  1576. })
  1577. function renderVmWithOptions (options, cb) {
  1578. renderToString(new Vue(options), (err, res) => {
  1579. expect(err).toBeNull()
  1580. cb(res)
  1581. })
  1582. }