ssr-string.spec.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. import Vue from '../../dist/vue.common.js'
  2. import { compileToFunctions } from '../../dist/compiler.js'
  3. import createRenderer from '../../dist/server-renderer.js'
  4. const { renderToString } = createRenderer()
  5. // TODO: test custom server-side directives
  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" 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" 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" 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 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 server-rendered="true" class="foo bar baz qux"></div>')
  53. done()
  54. })
  55. })
  56. it('dynamic style', done => {
  57. renderVmWithOptions({
  58. template: '<div style="background-color:black" :style="{ fontSize: fontSize + \'px\', color: color }"></div>',
  59. data: {
  60. fontSize: 14,
  61. color: 'red'
  62. }
  63. }, result => {
  64. expect(result).toContain(
  65. '<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>'
  66. )
  67. done()
  68. })
  69. })
  70. it('text interpolation', done => {
  71. renderVmWithOptions({
  72. template: '<div>{{ foo }} side {{ bar }}</div>',
  73. data: {
  74. foo: 'server',
  75. bar: 'rendering'
  76. }
  77. }, result => {
  78. expect(result).toContain('<div server-rendered="true">server side rendering</div>')
  79. done()
  80. })
  81. })
  82. it('child component (hoc)', done => {
  83. renderVmWithOptions({
  84. template: '<child class="foo" :msg="msg"></child>',
  85. data: {
  86. msg: 'hello'
  87. },
  88. components: {
  89. child: {
  90. props: ['msg'],
  91. data () {
  92. return { name: 'bar' }
  93. },
  94. render () {
  95. const h = this.$createElement
  96. return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`])
  97. }
  98. }
  99. }
  100. }, result => {
  101. expect(result).toContain('<div server-rendered="true" class="foo bar">hello bar</div>')
  102. done()
  103. })
  104. })
  105. it('has correct lifecycle during render', done => {
  106. let lifecycleCount = 1
  107. renderVmWithOptions({
  108. template: '<div><span>{{ val }}</span><test></test></div>',
  109. data: {
  110. val: 'hi'
  111. },
  112. init () {
  113. expect(lifecycleCount++).toBe(1)
  114. },
  115. created () {
  116. this.val = 'hello'
  117. expect(this.val).toBe('hello')
  118. expect(lifecycleCount++).toBe(2)
  119. },
  120. components: {
  121. test: {
  122. init () {
  123. expect(lifecycleCount++).toBe(3)
  124. },
  125. created () {
  126. expect(lifecycleCount++).toBe(4)
  127. },
  128. render () {
  129. expect(lifecycleCount++).toBeGreaterThan(4)
  130. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  131. }
  132. }
  133. }
  134. }, result => {
  135. expect(result).toContain(
  136. '<div server-rendered="true">' +
  137. '<span>hello</span>' +
  138. '<span class="b">testAsync</span>' +
  139. '</div>'
  140. )
  141. done()
  142. })
  143. })
  144. it('renders asynchronous component', done => {
  145. renderVmWithOptions({
  146. template: `
  147. <div>
  148. <test-async></test-async>
  149. </div>
  150. `,
  151. components: {
  152. testAsync (resolve) {
  153. resolve({
  154. render () {
  155. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  156. }
  157. })
  158. }
  159. }
  160. }, result => {
  161. expect(result).toContain('<div server-rendered="true"><span class="b">testAsync</span></div>')
  162. done()
  163. })
  164. })
  165. it('renders asynchronous component (hoc)', done => {
  166. renderVmWithOptions({
  167. template: '<test-async></test-async>',
  168. components: {
  169. testAsync (resolve) {
  170. resolve({
  171. render () {
  172. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  173. }
  174. })
  175. }
  176. }
  177. }, result => {
  178. expect(result).toContain('<span server-rendered="true" class="b">testAsync</span>')
  179. done()
  180. })
  181. })
  182. it('renders nested asynchronous component', done => {
  183. renderVmWithOptions({
  184. template: `
  185. <div>
  186. <test-async></test-async>
  187. </div>
  188. `,
  189. components: {
  190. testAsync (resolve) {
  191. const options = compileToFunctions(`
  192. <span class="b">
  193. <test-sub-async></test-sub-async>
  194. </span>
  195. `, { preserveWhitespace: false })
  196. options.components = {
  197. testSubAsync (resolve) {
  198. resolve({
  199. render () {
  200. return this.$createElement('div', { class: ['c'] }, 'testSubAsync')
  201. }
  202. })
  203. }
  204. }
  205. resolve(options)
  206. }
  207. }
  208. }, result => {
  209. expect(result).toContain('<div server-rendered="true"><span class="b"><div class="c">testSubAsync</div></span></div>')
  210. done()
  211. })
  212. })
  213. it('everything together', done => {
  214. renderVmWithOptions({
  215. template: `
  216. <div>
  217. <p class="hi">yoyo</p>
  218. <div id="ho" :class="{ red: isRed }"></div>
  219. <span>{{ test }}</span>
  220. <input :value="test">
  221. <img :src="imageUrl">
  222. <test></test>
  223. <test-async></test-async>
  224. </div>
  225. `,
  226. data: {
  227. test: 'hi',
  228. isRed: true,
  229. imageUrl: 'https://vuejs.org/images/logo.png'
  230. },
  231. components: {
  232. test: {
  233. render () {
  234. return this.$createElement('div', { class: ['a'] }, 'test')
  235. }
  236. },
  237. testAsync (resolve) {
  238. resolve({
  239. render () {
  240. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  241. }
  242. })
  243. }
  244. }
  245. }, result => {
  246. expect(result).toContain(
  247. '<div server-rendered="true">' +
  248. '<p class="hi">yoyo</p>' +
  249. '<div id="ho" class="red"></div>' +
  250. '<span>hi</span>' +
  251. '<input value="hi">' +
  252. '<img src="https://vuejs.org/images/logo.png">' +
  253. '<div class="a">test</div>' +
  254. '<span class="b">testAsync</span>' +
  255. '</div>'
  256. )
  257. done()
  258. })
  259. })
  260. it('normal attr', done => {
  261. renderVmWithOptions({
  262. template: `
  263. <div>
  264. <span :test="'ok'">hello</span>
  265. <span :test="null">hello</span>
  266. <span :test="false">hello</span>
  267. <span :test="true">hello</span>
  268. <span :test="0">hello</span>
  269. </div>
  270. `
  271. }, result => {
  272. expect(result).toContain(
  273. '<div server-rendered="true">' +
  274. '<span test="ok">hello</span>' +
  275. '<span>hello</span>' +
  276. '<span>hello</span>' +
  277. '<span test="true">hello</span>' +
  278. '<span test="0">hello</span>' +
  279. '</div>'
  280. )
  281. done()
  282. })
  283. })
  284. it('enumrated attr', done => {
  285. renderVmWithOptions({
  286. template: `
  287. <div>
  288. <span :draggable="true">hello</span>
  289. <span :draggable="'ok'">hello</span>
  290. <span :draggable="null">hello</span>
  291. <span :draggable="false">hello</span>
  292. <span :draggable="''">hello</span>
  293. <span :draggable="'false'">hello</span>
  294. </div>
  295. `
  296. }, result => {
  297. expect(result).toContain(
  298. '<div server-rendered="true">' +
  299. '<span draggable="true">hello</span>' +
  300. '<span draggable="true">hello</span>' +
  301. '<span draggable="false">hello</span>' +
  302. '<span draggable="false">hello</span>' +
  303. '<span draggable="true">hello</span>' +
  304. '<span draggable="false">hello</span>' +
  305. '</div>'
  306. )
  307. done()
  308. })
  309. })
  310. it('boolean attr', done => {
  311. renderVmWithOptions({
  312. template: `
  313. <div>
  314. <span :disabled="true">hello</span>
  315. <span :disabled="'ok'">hello</span>
  316. <span :disabled="null">hello</span>
  317. <span :disabled="''">hello</span>
  318. </div>
  319. `
  320. }, result => {
  321. expect(result).toContain(
  322. '<div server-rendered="true">' +
  323. '<span disabled="disabled">hello</span>' +
  324. '<span disabled="disabled">hello</span>' +
  325. '<span>hello</span>' +
  326. '<span disabled="disabled">hello</span>' +
  327. '</div>'
  328. )
  329. done()
  330. })
  331. })
  332. it('v-bind object', done => {
  333. renderVmWithOptions({
  334. data: {
  335. test: { id: 'a', class: 'b', value: 'c' }
  336. },
  337. template: '<input v-bind="test">'
  338. }, result => {
  339. expect(result).toContain('<input id="a" class="b" server-rendered="true" value="c">')
  340. done()
  341. })
  342. })
  343. it('should catch error', done => {
  344. renderToString(new Vue({
  345. render () {
  346. throw new Error('oops')
  347. }
  348. }), err => {
  349. expect(err instanceof Error).toBe(true)
  350. done()
  351. })
  352. })
  353. })
  354. function renderVmWithOptions (options, cb) {
  355. const res = compileToFunctions(options.template, {
  356. preserveWhitespace: false
  357. })
  358. Object.assign(options, res)
  359. delete options.template
  360. renderToString(new Vue(options), (err, res) => {
  361. expect(err).toBeNull()
  362. cb(res)
  363. })
  364. }