ssr.string.spec.js 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  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', () => {
  8. expect(renderVmWithOptions({
  9. template: '<div id="foo" bar="123"></div>'
  10. })).toContain('<div id="foo" bar="123" server-rendered="true"></div>')
  11. })
  12. it('unary tags', () => {
  13. expect(renderVmWithOptions({
  14. template: '<input value="123">'
  15. })).toContain('<input value="123" server-rendered="true">')
  16. })
  17. it('dynamic attributes', () => {
  18. expect(renderVmWithOptions({
  19. template: '<div qux="quux" :id="foo" :bar="baz"></div>',
  20. data: {
  21. foo: 'hi',
  22. baz: 123
  23. }
  24. })).toContain('<div qux="quux" id="hi" bar="123" server-rendered="true"></div>')
  25. })
  26. it('static class', () => {
  27. expect(renderVmWithOptions({
  28. template: '<div class="foo bar"></div>'
  29. })).toContain('<div server-rendered="true" class="foo bar"></div>')
  30. })
  31. it('dynamic class', () => {
  32. expect(renderVmWithOptions({
  33. template: '<div class="foo bar" :class="[a, { qux: hasQux, quux: hasQuux }]"></div>',
  34. data: {
  35. a: 'baz',
  36. hasQux: true,
  37. hasQuux: false
  38. }
  39. })).toContain('<div server-rendered="true" class="foo bar baz qux"></div>')
  40. })
  41. it('dynamic style', () => {
  42. expect(renderVmWithOptions({
  43. template: '<div style="background-color:black" :style="{ fontSize: fontSize + \'px\', color: color }"></div>',
  44. data: {
  45. fontSize: 14,
  46. color: 'red'
  47. }
  48. })).toContain(
  49. '<div server-rendered="true" style="font-size:14px;color:red;background-color:black"></div>'
  50. )
  51. })
  52. it('text interpolation', () => {
  53. expect(renderVmWithOptions({
  54. template: '<div>{{ foo }} side {{ bar }}</div>',
  55. data: {
  56. foo: 'server',
  57. bar: 'rendering'
  58. }
  59. })).toContain('<div server-rendered="true">server side rendering</div>')
  60. })
  61. it('child component (hoc)', () => {
  62. expect(renderVmWithOptions({
  63. template: '<child class="foo" :msg="msg"></child>',
  64. data: {
  65. msg: 'hello'
  66. },
  67. components: {
  68. child: {
  69. props: ['msg'],
  70. data () {
  71. return { name: 'bar' }
  72. },
  73. render () {
  74. const h = this.$createElement
  75. return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`])
  76. }
  77. }
  78. }
  79. })).toContain('<div server-rendered="true" class="foo bar">hello bar</div>')
  80. })
  81. it('has correct lifecycle during render', () => {
  82. let lifecycleCount = 1
  83. expect(renderVmWithOptions({
  84. template: '<div><span>{{ val }}</span><test></test></div>',
  85. data: {
  86. val: 'hi'
  87. },
  88. init () {
  89. expect(lifecycleCount++).toBe(1)
  90. },
  91. created () {
  92. this.val = 'hello'
  93. expect(this.val).toBe('hello')
  94. expect(lifecycleCount++).toBe(2)
  95. },
  96. components: {
  97. test: {
  98. init () {
  99. expect(lifecycleCount++).toBe(3)
  100. },
  101. created () {
  102. expect(lifecycleCount++).toBe(4)
  103. },
  104. render () {
  105. expect(lifecycleCount++).toBeGreaterThan(4)
  106. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  107. }
  108. }
  109. }
  110. })).toContain(
  111. '<div server-rendered="true">' +
  112. '<span>hello</span>' +
  113. '<span class="b">testAsync</span>' +
  114. '</div>'
  115. )
  116. })
  117. it('renders asynchronous component', () => {
  118. expect(renderVmWithOptions({
  119. template: `
  120. <div>
  121. <test-async></test-async>
  122. </div>
  123. `,
  124. components: {
  125. testAsync (resolve) {
  126. return resolve({
  127. render () {
  128. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  129. }
  130. })
  131. }
  132. }
  133. })).toContain('<div server-rendered="true"><span class="b">testAsync</span></div>')
  134. })
  135. it('renders asynchronous component (hoc)', () => {
  136. expect(renderVmWithOptions({
  137. template: '<test-async></test-async>',
  138. components: {
  139. testAsync (resolve) {
  140. return resolve({
  141. render () {
  142. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  143. }
  144. })
  145. }
  146. }
  147. })).toContain('<span server-rendered="true" class="b">testAsync</span>')
  148. })
  149. it('renders nested asynchronous component', () => {
  150. expect(renderVmWithOptions({
  151. template: `
  152. <div>
  153. <test-async></test-async>
  154. </div>
  155. `,
  156. components: {
  157. testAsync (resolve) {
  158. const options = compileToFunctions(`
  159. <span class="b">
  160. <test-sub-async></test-sub-async>
  161. </span>
  162. `, { preserveWhitespace: false })
  163. options.components = {
  164. testSubAsync (resolve) {
  165. return resolve({
  166. render () {
  167. return this.$createElement('div', { class: ['c'] }, 'testSubAsync')
  168. }
  169. })
  170. }
  171. }
  172. return resolve(options)
  173. }
  174. }
  175. })).toContain(
  176. '<div server-rendered="true"><span class="b"><div class="c">testSubAsync</div></span></div>'
  177. )
  178. })
  179. it('everything together', () => {
  180. expect(renderVmWithOptions({
  181. template: `
  182. <div>
  183. <p class="hi">yoyo</p>
  184. <div id="ho" :class="{ red: isRed }"></div>
  185. <span>{{ test }}</span>
  186. <input :value="test">
  187. <img :src="imageUrl">
  188. <test></test>
  189. <test-async></test-async>
  190. </div>
  191. `,
  192. data: {
  193. test: 'hi',
  194. isRed: true,
  195. imageUrl: 'https://vuejs.org/images/logo.png'
  196. },
  197. components: {
  198. test: {
  199. render () {
  200. return this.$createElement('div', { class: ['a'] }, 'test')
  201. }
  202. },
  203. testAsync (resolve) {
  204. return resolve({
  205. render () {
  206. return this.$createElement('span', { class: ['b'] }, 'testAsync')
  207. }
  208. })
  209. }
  210. }
  211. })).toContain(
  212. '<div server-rendered="true">' +
  213. '<p class="hi">yoyo</p>' +
  214. '<div id="ho" class="red"></div>' +
  215. '<span>hi</span>' +
  216. '<input value="hi">' +
  217. '<img src="https://vuejs.org/images/logo.png">' +
  218. '<div class="a">test</div>' +
  219. '<span class="b">testAsync</span>' +
  220. '</div>'
  221. )
  222. })
  223. it('normal attr', () => {
  224. expect(renderVmWithOptions({
  225. template: `
  226. <div>
  227. <span :test="'ok'">hello</span>
  228. <span :test="null">hello</span>
  229. <span :test="false">hello</span>
  230. <span :test="true">hello</span>
  231. <span :test="0">hello</span>
  232. </div>
  233. `
  234. })).toContain(
  235. '<div server-rendered="true">' +
  236. '<span test="ok">hello</span>' +
  237. '<span>hello</span>' +
  238. '<span>hello</span>' +
  239. '<span test="true">hello</span>' +
  240. '<span test="0">hello</span>' +
  241. '</div>'
  242. )
  243. })
  244. it('enumrated attr', () => {
  245. expect(renderVmWithOptions({
  246. template: `
  247. <div>
  248. <span :draggable="true">hello</span>
  249. <span :draggable="'ok'">hello</span>
  250. <span :draggable="null">hello</span>
  251. <span :draggable="false">hello</span>
  252. <span :draggable="''">hello</span>
  253. <span :draggable="'false'">hello</span>
  254. </div>
  255. `
  256. })).toContain(
  257. '<div server-rendered="true">' +
  258. '<span draggable="true">hello</span>' +
  259. '<span draggable="true">hello</span>' +
  260. '<span draggable="false">hello</span>' +
  261. '<span draggable="false">hello</span>' +
  262. '<span draggable="true">hello</span>' +
  263. '<span draggable="false">hello</span>' +
  264. '</div>'
  265. )
  266. })
  267. it('boolean attr', () => {
  268. expect(renderVmWithOptions({
  269. template: `
  270. <div>
  271. <span :disabled="true">hello</span>
  272. <span :disabled="'ok'">hello</span>
  273. <span :disabled="null">hello</span>
  274. <span :disabled="''">hello</span>
  275. </div>
  276. `
  277. })).toContain(
  278. '<div server-rendered="true">' +
  279. '<span disabled="disabled">hello</span>' +
  280. '<span disabled="disabled">hello</span>' +
  281. '<span>hello</span>' +
  282. '<span disabled="disabled">hello</span>' +
  283. '</div>'
  284. )
  285. })
  286. })
  287. function renderVmWithOptions (options) {
  288. const res = compileToFunctions(options.template, {
  289. preserveWhitespace: false
  290. })
  291. Object.assign(options, res)
  292. delete options.template
  293. return renderToString(new Vue(options))
  294. }