ssr-string.spec.js 41 KB

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