Skip to content

Commit da7aadc

Browse files
authored
feat(renderToString) Add renderToString method (#435)
1 parent 6ea4b10 commit da7aadc

29 files changed

+556
-131
lines changed

docs/en/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* [API](api/README.md)
1919
* [mount](api/mount.md)
2020
* [shallow](api/shallow.md)
21+
* [renderToString](api/renderToString.md)
2122
* [Mounting Options](api/options.md)
2223
- [context](api/options.md#context)
2324
- [slots](api/options.md#slots)

docs/en/SUMMARY.md

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* [API](api/README.md)
1515
* [mount](api/mount.md)
1616
* [shallow](api/shallow.md)
17+
* [renderToString](api/renderToString.md)
1718
* [Mounting Options](api/options.md)
1819
- [context](api/options.md#context)
1920
- [slots](api/options.md#slots)

docs/en/api/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* [mount](./mount.md)
44
* [shallow](./shallow.md)
5+
* [renderToString](./renderToString.md)
56
* [Mounting Options](./options.md)
67
- [context](./options.md#context)
78
- [slots](./options.md#slots)

docs/en/api/renderToString.md

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# `renderToString(component {, options}])`
2+
3+
- **Arguments:**
4+
5+
- `{Component} component`
6+
- `{Object} options`
7+
- `{Object} context`
8+
- `{Array<Component|Object>|Component} children`
9+
- `{Object} slots`
10+
- `{Array<Componet|Object>|Component|String} default`
11+
- `{Array<Componet|Object>|Component|String} named`
12+
- `{Object} mocks`
13+
- `{Object|Array<string>} stubs`
14+
- `{Vue} localVue`
15+
16+
- **Returns:** `{string}`
17+
18+
- **Options:**
19+
20+
See [options](./options.md)
21+
22+
- **Usage:**
23+
24+
Renders a component to HTML.
25+
26+
`renderToString` uses [`vue-server-renderer`](https://ssr.vuejs.org/en/basic.html) under the hood, to render a component to HTML.
27+
28+
**Without options:**
29+
30+
```js
31+
import { renderToString } from '@vue/test-utils'
32+
import { expect } from 'chai'
33+
import Foo from './Foo.vue'
34+
35+
describe('Foo', () => {
36+
it('renders a div', () => {
37+
const renderedString = renderToString(Foo)
38+
expect(renderedString).toContain('<div></div>')
39+
})
40+
})
41+
```
42+
43+
**With Vue options:**
44+
45+
```js
46+
import { renderToString } from '@vue/test-utils'
47+
import { expect } from 'chai'
48+
import Foo from './Foo.vue'
49+
50+
describe('Foo', () => {
51+
it('renders a div', () => {
52+
const renderedString = renderToString(Foo, {
53+
propsData: {
54+
color: 'red'
55+
}
56+
})
57+
expect(renderedString).toContain('red')
58+
})
59+
})
60+
```
61+
62+
**Default and named slots:**
63+
64+
```js
65+
import { renderToString } from '@vue/test-utils'
66+
import { expect } from 'chai'
67+
import Foo from './Foo.vue'
68+
import Bar from './Bar.vue'
69+
import FooBar from './FooBar.vue'
70+
71+
describe('Foo', () => {
72+
it('renders a div', () => {
73+
const renderedString = renderToString(Foo, {
74+
slots: {
75+
default: [Bar, FooBar],
76+
fooBar: FooBar, // Will match <slot name="FooBar" />,
77+
foo: '<div />'
78+
}
79+
})
80+
expect(renderedString).toContain('<div></div>')
81+
})
82+
})
83+
```
84+
85+
**Stubbing global properties:**
86+
87+
```js
88+
import { renderToString } from '@vue/test-utils'
89+
import { expect } from 'chai'
90+
import Foo from './Foo.vue'
91+
92+
describe('Foo', () => {
93+
it('renders a div', () => {
94+
const $route = { path: 'http://www.example-path.com' }
95+
const renderedString = renderToString(Foo, {
96+
mocks: {
97+
$route
98+
}
99+
})
100+
expect(renderedString).toContain($route.path)
101+
})
102+
})
103+
```

flow/modules.flow.js

+4
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ declare module 'lodash/cloneDeep' {
1515
declare module 'vue-template-compiler' {
1616
declare module.exports: any;
1717
}
18+
19+
declare module 'vue-server-renderer' {
20+
declare module.exports: any;
21+
}

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"vue-class-component": "^6.1.2",
8989
"vue-loader": "^13.6.2",
9090
"vue-router": "^3.0.1",
91+
"vue-server-renderer": "2.5.13",
9192
"vue-template-compiler": "^2.5.13",
9293
"vuetify": "^0.16.9",
9394
"vuex": "^3.0.1",
@@ -96,6 +97,7 @@
9697
},
9798
"peerDependencies": {
9899
"vue": "2.x",
100+
"vue-server-renderer": "2.x",
99101
"vue-template-compiler": "^2.x"
100102
},
101103
"dependencies": {

scripts/test-compat.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set -e
44

55
test_version_number(){
66
echo "running unit tests with Vue $1"
7-
yarn add vue@$1 vue-template-compiler@$1
7+
yarn add vue@$1 vue-template-compiler@$1 vue-server-renderer@$1
88
yarn test:unit
99
yarn test:unit:karma
1010
}

src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import shallow from './shallow'
22
import mount from './mount'
3+
import renderToString from './renderToString'
34
import createLocalVue from './create-local-vue'
45
import TransitionStub from './components/TransitionStub'
56
import TransitionGroupStub from './components/TransitionGroupStub'
@@ -11,6 +12,7 @@ export default {
1112
config,
1213
mount,
1314
shallow,
15+
renderToString,
1416
TransitionStub,
1517
TransitionGroupStub,
1618
RouterLinkStub

src/lib/add-slots.js

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ function addSlots (vm: Component, slots: Object): void {
5454

5555
if (Array.isArray(slots[key])) {
5656
slots[key].forEach((slotValue) => {
57+
if (!isValidSlot(slotValue)) {
58+
throwError('slots[key] must be a Component, string or an array of Components')
59+
}
5760
addSlotToVm(vm, key, slotValue)
5861
})
5962
} else {

src/mount.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import errorHandler from './lib/error-handler'
1111
import { findAllVueComponentsFromVm } from './lib/find-vue-components'
1212

1313
Vue.config.productionTip = false
14-
Vue.config.errorHandler = errorHandler
1514
Vue.config.devtools = false
15+
Vue.config.errorHandler = errorHandler
1616

1717
export default function mount (component: Component, options: Options = {}): VueWrapper {
1818
// Remove cached constructor

src/renderToString.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// @flow
2+
3+
import Vue from 'vue'
4+
import createInstance from './lib/create-instance'
5+
import './lib/polyfills/object-assign-polyfill'
6+
import { throwError } from './lib/util'
7+
8+
Vue.config.productionTip = false
9+
Vue.config.devtools = false
10+
11+
export default function renderToString (component: Component, options: Options = {}): string {
12+
let renderer
13+
try {
14+
renderer = require('vue-server-renderer').createRenderer()
15+
} catch (e) {}
16+
17+
if (!renderer) {
18+
throwError('renderToString must be run in node. It cannot be run in a browser')
19+
}
20+
// Remove cached constructor
21+
delete component._Ctor
22+
23+
if (options.attachToDocument) {
24+
throwError('you cannot use attachToDocument with renderToString')
25+
}
26+
27+
const vm = createInstance(component, options)
28+
29+
let renderedString = ''
30+
31+
// $FlowIgnore
32+
renderer.renderToString(vm, (err, res) => {
33+
if (err) {
34+
console.log(err)
35+
}
36+
renderedString = res
37+
})
38+
return renderedString
39+
}

test/resources/test-utils.js

+29-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
/* global describe, it*/
22

33
import Vue from 'vue'
4-
import { shallow, mount } from '~vue-test-utils'
4+
import { shallow, mount, renderToString } from '~vue-test-utils'
55

66
export const vueVersion = Number(`${Vue.version.split('.')[0]}.${Vue.version.split('.')[1]}`)
77

8+
export const isRunningJSDOM = navigator.userAgent.includes && navigator.userAgent.includes('jsdom')
9+
810
export function injectSupported () {
911
return vueVersion > 2.2
1012
}
@@ -21,20 +23,43 @@ export function functionalSFCsSupported () {
2123
return vueVersion >= 2.5
2224
}
2325

26+
const shallowAndMount = [mount, shallow]
27+
const shallowMountAndRender = isRunningJSDOM
28+
? [mount, shallow, renderToString]
29+
: [mount, shallow]
30+
2431
export function describeWithShallowAndMount (spec, cb) {
25-
;[mount, shallow].forEach(method => {
32+
shallowAndMount.forEach(method => {
2633
describe(`${spec} with ${method.name}`, () => cb(method))
2734
})
2835
}
2936

3037
describeWithShallowAndMount.skip = function (spec, cb) {
31-
;[mount, shallow].forEach(method => {
38+
shallowAndMount.forEach(method => {
3239
describe.skip(`${spec} with ${method.name}`, () => cb(method))
3340
})
3441
}
3542

3643
describeWithShallowAndMount.only = function (spec, cb) {
37-
;[mount, shallow].forEach(method => {
44+
shallowAndMount.forEach(method => {
45+
describe.only(`${spec} with ${method.name}`, () => cb(method))
46+
})
47+
}
48+
49+
export function describeWithMountingMethods (spec, cb) {
50+
shallowMountAndRender.forEach(method => {
51+
describe(`${spec} with ${method.name}`, () => cb(method))
52+
})
53+
}
54+
55+
describeWithMountingMethods.skip = function (spec, cb) {
56+
shallowMountAndRender.forEach(method => {
57+
describe.skip(`${spec} with ${method.name}`, () => cb(method))
58+
})
59+
}
60+
61+
describeWithMountingMethods.only = function (spec, cb) {
62+
shallowMountAndRender.forEach(method => {
3863
describe.only(`${spec} with ${method.name}`, () => cb(method))
3964
})
4065
}

test/setup/karma.conf.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ module.exports = function (config) {
77
reporters: ['spec'],
88
files: [
99
'../../node_modules/babel-polyfill/dist/polyfill.js',
10-
'../specs/**/*.+(vue|js)'
10+
'load-tests.js'
1111
],
1212
preprocessors: {
13-
'../specs/**/*.+(vue|js)': ['webpack', 'sourcemap']
13+
'load-tests.js': ['webpack', 'sourcemap']
1414
},
1515
client: { mocha: { timeout: 20000 }},
1616
webpack: webpackConfig,

test/setup/load-tests.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const testsContext = require.context('../specs', true, /\.spec\.(js|vue)$/)
2+
3+
testsContext.keys().forEach(testsContext)

test/setup/webpack.test.config.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,8 @@ module.exports = {
3838
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
3939
devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
4040
},
41-
devtool: '#inline-cheap-module-source-map'
41+
devtool: '#inline-cheap-module-source-map',
42+
node: {
43+
fs: 'empty'
44+
}
4245
}

test/specs/components/TransitionGroupStub.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ describe('TransitionGroupStub', () => {
2222
}),
2323
watch: {
2424
someWatchedData (newData) {
25-
console.log('asd')
2625
this.someData = newData
2726
}
2827
},
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { compileToFunctions } from 'vue-template-compiler'
2-
import { describeWithShallowAndMount } from '~resources/test-utils'
2+
import {
3+
describeWithShallowAndMount,
4+
isRunningJSDOM
5+
} from '~resources/test-utils'
6+
import { renderToString } from '~vue-test-utils'
37

48
describeWithShallowAndMount('options.attachToDocument', (mountingMethod) => {
59
it('returns VueWrapper with attachedToDocument set to true when passed attachToDocument in options', () => {
@@ -8,3 +12,16 @@ describeWithShallowAndMount('options.attachToDocument', (mountingMethod) => {
812
expect(wrapper.options.attachedToDocument).to.equal(true)
913
})
1014
})
15+
16+
describe('options.attachToDocument with renderToString', () => {
17+
it('throws error that renderToString does not accept attachToDocument', () => {
18+
// renderToString can only be run in node
19+
if (!isRunningJSDOM) {
20+
return
21+
}
22+
const compiled = compileToFunctions('<div><input /></div>')
23+
const fn = () => renderToString(compiled, { attachToDocument: true })
24+
const message = '[vue-test-utils]: you cannot use attachToDocument with renderToString'
25+
expect(fn).to.throw().with.property('message', message)
26+
})
27+
})

test/specs/mounting-options/attrs.spec.js

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import { compileToFunctions } from 'vue-template-compiler'
22
import { attrsSupported } from '~resources/test-utils'
3-
import { describeWithShallowAndMount } from '~resources/test-utils'
3+
import {
4+
describeWithMountingMethods,
5+
itSkipIf
6+
} from '~resources/test-utils'
47

5-
describeWithShallowAndMount('options.attrs', (mountingMethod) => {
6-
it('handles inherit attrs', () => {
8+
describeWithMountingMethods('options.attrs', (mountingMethod) => {
9+
itSkipIf(mountingMethod.name === 'renderToString',
10+
'handles inherit attrs', () => {
711
if (!attrsSupported()) return
812
const wrapper = mountingMethod(compileToFunctions('<p :id="anAttr" />'), {
913
attrs: {
@@ -15,7 +19,8 @@ describeWithShallowAndMount('options.attrs', (mountingMethod) => {
1519
expect(wrapper.vm.$attrs.anAttr).to.equal('an attribute')
1620
})
1721

18-
it('defines attrs as empty object even when not passed', () => {
22+
itSkipIf(mountingMethod.name === 'renderToString',
23+
'defines attrs as empty object even when not passed', () => {
1924
const wrapper = mountingMethod(compileToFunctions('<p />'))
2025
expect(wrapper.vm.$attrs).to.deep.equal({})
2126
})

0 commit comments

Comments
 (0)