import Vue from '../../dist/vue.runtime.common.js' import { createRenderer } from '../../packages/vue-server-renderer' const { renderToString } = createRenderer() describe('SSR: renderToString', () => { it('static attributes', done => { renderVmWithOptions({ template: '
' }, result => { expect(result).toContain('
') done() }) }) it('unary tags', done => { renderVmWithOptions({ template: '' }, result => { expect(result).toContain('') done() }) }) it('dynamic attributes', done => { renderVmWithOptions({ template: '
', data: { foo: 'hi', baz: 123 } }, result => { expect(result).toContain('
') done() }) }) it('static class', done => { renderVmWithOptions({ template: '
' }, result => { expect(result).toContain('
') done() }) }) it('dynamic class', done => { renderVmWithOptions({ template: '
', data: { a: 'baz', hasQux: true, hasQuux: false } }, result => { expect(result).toContain('
') done() }) }) it('custome component class', done => { renderVmWithOptions({ template: '
', components: { cmp: { render: h => h('div', 'test') } } }, result => { expect(result).toContain('
test
') done() }) }) it('nested component class', done => { renderVmWithOptions({ template: '', data: { cls: { 'success': 1 }}, components: { cmp: { render: h => h('div', [h('nested', { staticClass: 'nested', 'class': { 'error': 1 }})]), components: { nested: { render: h => h('div', { staticClass: 'inner' }, 'test') } } } } }, result => { expect(result).toContain('
' + '
test
' + '
') done() }) }) it('dynamic style', done => { renderVmWithOptions({ template: '
', data: { fontSize: 14, color: 'red' } }, result => { expect(result).toContain( '
' ) done() }) }) it('dynamic string style', done => { renderVmWithOptions({ template: '
', data: { style: 'color:red' } }, result => { expect(result).toContain( '
' ) done() }) }) it('custom component style', done => { renderVmWithOptions({ template: '
', data: { style: 'color:red' }, components: { comp: { template: '
' } } }, result => { expect(result).toContain( '
' ) done() }) }) it('nested custom component style', done => { renderVmWithOptions({ template: '', data: { style: 'color:red' }, components: { comp: { template: '', components: { nested: { template: '
' } } } } }, result => { expect(result).toContain( '
' ) done() }) }) it('component style not passed to child', done => { renderVmWithOptions({ template: '', data: { style: 'color:red' }, components: { comp: { template: '
' } } }, result => { expect(result).toContain( '
' ) done() }) }) it('component style not passed to slot', done => { renderVmWithOptions({ template: '', data: { style: 'color:red' }, components: { comp: { template: '
' } } }, result => { expect(result).toContain( '
' ) done() }) }) it('attrs merging on components', done => { const Test = { render: h => h('div', { attrs: { id: 'a' } }) } renderVmWithOptions({ render: h => h(Test, { attrs: { id: 'b', name: 'c' } }) }, res => { expect(res).toContain( '
' ) done() }) }) it('domProps merging on components', done => { const Test = { render: h => h('div', { domProps: { innerHTML: 'a' } }) } renderVmWithOptions({ render: h => h(Test, { domProps: { innerHTML: 'b', value: 'c' } }) }, res => { expect(res).toContain( '
b
' ) done() }) }) it('text interpolation', done => { renderVmWithOptions({ template: '
{{ foo }} side {{ bar }}
', data: { foo: 'server', bar: 'rendering' } }, result => { expect(result).toContain('
server side <span>rendering</span>
') done() }) }) it('v-html', done => { renderVmWithOptions({ template: '
', data: { text: 'foo' } }, result => { expect(result).toContain('
foo
') done() }) }) it('v-text', done => { renderVmWithOptions({ template: '
', data: { text: 'foo' } }, result => { expect(result).toContain('
<span>foo</span>
') done() }) }) it('child component (hoc)', done => { renderVmWithOptions({ template: '', data: { msg: 'hello' }, components: { child: { props: ['msg'], data () { return { name: 'bar' } }, render () { const h = this.$createElement return h('div', { class: ['bar'] }, [`${this.msg} ${this.name}`]) } } } }, result => { expect(result).toContain('
hello bar
') done() }) }) it('has correct lifecycle during render', done => { let lifecycleCount = 1 renderVmWithOptions({ template: '
{{ val }}
', data: { val: 'hi' }, beforeCreate () { expect(lifecycleCount++).toBe(1) }, created () { this.val = 'hello' expect(this.val).toBe('hello') expect(lifecycleCount++).toBe(2) }, components: { test: { beforeCreate () { expect(lifecycleCount++).toBe(3) }, created () { expect(lifecycleCount++).toBe(4) }, render () { expect(lifecycleCount++).toBeGreaterThan(4) return this.$createElement('span', { class: ['b'] }, 'testAsync') } } } }, result => { expect(result).toContain( '
' + 'hello' + 'testAsync' + '
' ) done() }) }) it('computed properties', done => { renderVmWithOptions({ template: '
{{ b }}
', data: { a: { b: 1 } }, computed: { b () { return this.a.b + 1 } }, created () { this.a.b = 2 expect(this.b).toBe(3) } }, result => { expect(result).toContain('
3
') done() }) }) it('renders asynchronous component', done => { renderVmWithOptions({ template: `
`, components: { testAsync (resolve) { resolve({ render () { return this.$createElement('span', { class: ['b'] }, 'testAsync') } }) } } }, result => { expect(result).toContain('
testAsync
') done() }) }) it('renders asynchronous component (hoc)', done => { renderVmWithOptions({ template: '', components: { testAsync (resolve) { resolve({ render () { return this.$createElement('span', { class: ['b'] }, 'testAsync') } }) } } }, result => { expect(result).toContain('testAsync') done() }) }) it('renders nested asynchronous component', done => { renderVmWithOptions({ template: `
`, components: { testAsync (resolve) { const options = { template: ` ` } options.components = { testSubAsync (resolve) { resolve({ render () { return this.$createElement('div', { class: ['c'] }, 'testSubAsync') } }) } } resolve(options) } } }, result => { expect(result).toContain('
testSubAsync
') done() }) }) it('everything together', done => { renderVmWithOptions({ template: `

yoyo

{{ test }}
`, data: { test: 'hi', isRed: true, imageUrl: 'https://vuejs.org/images/logo.png' }, components: { test: { render () { return this.$createElement('div', { class: ['a'] }, 'test') } }, testAsync (resolve) { resolve({ render () { return this.$createElement('span', { class: ['b'] }, 'testAsync') } }) } } }, result => { expect(result).toContain( '
' + '

yoyo

' + '
' + 'hi ' + ' ' + ' ' + '
test
' + 'testAsync' + '
' ) done() }) }) it('normal attr', done => { renderVmWithOptions({ template: `
hello hello hello hello hello
` }, result => { expect(result).toContain( '
' + 'hello ' + 'hello ' + 'hello ' + 'hello ' + 'hello' + '
' ) done() }) }) it('enumrated attr', done => { renderVmWithOptions({ template: `
hello hello hello hello hello hello
` }, result => { expect(result).toContain( '
' + 'hello ' + 'hello ' + 'hello ' + 'hello ' + 'hello ' + 'hello' + '
' ) done() }) }) it('boolean attr', done => { renderVmWithOptions({ template: `
hello hello hello hello
` }, result => { expect(result).toContain( '
' + 'hello ' + 'hello ' + 'hello ' + 'hello' + '
' ) done() }) }) it('v-bind object', done => { renderVmWithOptions({ data: { test: { id: 'a', class: ['a', 'b'], value: 'c' } }, template: '' }, result => { expect(result).toContain('') done() }) }) it('custom directives', done => { const renderer = createRenderer({ directives: { 'class-prefixer': (node, dir) => { if (node.data.class) { node.data.class = `${dir.value}-${node.data.class}` } if (node.data.staticClass) { node.data.staticClass = `${dir.value}-${node.data.staticClass}` } } } }) renderer.renderToString(new Vue({ render () { const h = this.$createElement return h('p', { class: 'class1', staticClass: 'class2', directives: [{ name: 'class-prefixer', value: 'my' }] }, ['hello world']) } }), (err, result) => { expect(err).toBeNull() expect(result).toContain('

hello world

') done() }) }) it('_scopeId', done => { renderVmWithOptions({ _scopeId: '_v-parent', template: '

', components: { child: { _scopeId: '_v-child', render () { const h = this.$createElement return h('div', null, [h('span', null, ['foo'])]) } } } }, result => { expect(result).toContain( '
' + '

' + '

foo
' + '

' + '
' ) done() }) }) it('_scopeId on slot content', done => { renderVmWithOptions({ _scopeId: '_v-parent', template: '

foo

', components: { child: { _scopeId: '_v-child', render () { const h = this.$createElement return h('div', null, this.$slots.default) } } } }, result => { expect(result).toContain( '
' + '

foo

' + '
' ) done() }) }) it('comment nodes', done => { renderVmWithOptions({ template: '
' }, result => { expect(result).toContain(`
`) done() }) }) it('should catch error', done => { Vue.config.silent = true renderToString(new Vue({ render () { throw new Error('oops') } }), err => { expect(err instanceof Error).toBe(true) Vue.config.silent = false done() }) }) }) function renderVmWithOptions (options, cb) { renderToString(new Vue(options), (err, res) => { expect(err).toBeNull() cb(res) }) }