Skip to content

Commit

Permalink
feat(renderToString) Add renderToString method (#435)
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyerburgh committed Feb 20, 2018
1 parent 6ea4b10 commit da7aadc
Show file tree
Hide file tree
Showing 29 changed files with 556 additions and 131 deletions.
1 change: 1 addition & 0 deletions docs/en/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* [API](api/README.md)
* [mount](api/mount.md)
* [shallow](api/shallow.md)
* [renderToString](api/renderToString.md)
* [Mounting Options](api/options.md)
- [context](api/options.md#context)
- [slots](api/options.md#slots)
Expand Down
1 change: 1 addition & 0 deletions docs/en/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* [API](api/README.md)
* [mount](api/mount.md)
* [shallow](api/shallow.md)
* [renderToString](api/renderToString.md)
* [Mounting Options](api/options.md)
- [context](api/options.md#context)
- [slots](api/options.md#slots)
Expand Down
1 change: 1 addition & 0 deletions docs/en/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* [mount](./mount.md)
* [shallow](./shallow.md)
* [renderToString](./renderToString.md)
* [Mounting Options](./options.md)
- [context](./options.md#context)
- [slots](./options.md#slots)
Expand Down
103 changes: 103 additions & 0 deletions docs/en/api/renderToString.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# `renderToString(component {, options}])`

- **Arguments:**

- `{Component} component`
- `{Object} options`
- `{Object} context`
- `{Array<Component|Object>|Component} children`
- `{Object} slots`
- `{Array<Componet|Object>|Component|String} default`
- `{Array<Componet|Object>|Component|String} named`
- `{Object} mocks`
- `{Object|Array<string>} stubs`
- `{Vue} localVue`

- **Returns:** `{string}`

- **Options:**

See [options](./options.md)

- **Usage:**

Renders a component to HTML.

`renderToString` uses [`vue-server-renderer`](https://ssr.vuejs.org/en/basic.html) under the hood, to render a component to HTML.

**Without options:**

```js
import { renderToString } from '@vue/test-utils'
import { expect } from 'chai'
import Foo from './Foo.vue'

describe('Foo', () => {
it('renders a div', () => {
const renderedString = renderToString(Foo)
expect(renderedString).toContain('<div></div>')
})
})
```

**With Vue options:**

```js
import { renderToString } from '@vue/test-utils'
import { expect } from 'chai'
import Foo from './Foo.vue'

describe('Foo', () => {
it('renders a div', () => {
const renderedString = renderToString(Foo, {
propsData: {
color: 'red'
}
})
expect(renderedString).toContain('red')
})
})
```

**Default and named slots:**

```js
import { renderToString } from '@vue/test-utils'
import { expect } from 'chai'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
import FooBar from './FooBar.vue'

describe('Foo', () => {
it('renders a div', () => {
const renderedString = renderToString(Foo, {
slots: {
default: [Bar, FooBar],
fooBar: FooBar, // Will match <slot name="FooBar" />,
foo: '<div />'
}
})
expect(renderedString).toContain('<div></div>')
})
})
```

**Stubbing global properties:**

```js
import { renderToString } from '@vue/test-utils'
import { expect } from 'chai'
import Foo from './Foo.vue'

describe('Foo', () => {
it('renders a div', () => {
const $route = { path: 'http://www.example-path.com' }
const renderedString = renderToString(Foo, {
mocks: {
$route
}
})
expect(renderedString).toContain($route.path)
})
})
```
4 changes: 4 additions & 0 deletions flow/modules.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@ declare module 'lodash/cloneDeep' {
declare module 'vue-template-compiler' {
declare module.exports: any;
}

declare module 'vue-server-renderer' {
declare module.exports: any;
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
"vue-class-component": "^6.1.2",
"vue-loader": "^13.6.2",
"vue-router": "^3.0.1",
"vue-server-renderer": "2.5.13",
"vue-template-compiler": "^2.5.13",
"vuetify": "^0.16.9",
"vuex": "^3.0.1",
Expand All @@ -96,6 +97,7 @@
},
"peerDependencies": {
"vue": "2.x",
"vue-server-renderer": "2.x",
"vue-template-compiler": "^2.x"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion scripts/test-compat.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e

test_version_number(){
echo "running unit tests with Vue $1"
yarn add vue@$1 vue-template-compiler@$1
yarn add vue@$1 vue-template-compiler@$1 vue-server-renderer@$1
yarn test:unit
yarn test:unit:karma
}
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import shallow from './shallow'
import mount from './mount'
import renderToString from './renderToString'
import createLocalVue from './create-local-vue'
import TransitionStub from './components/TransitionStub'
import TransitionGroupStub from './components/TransitionGroupStub'
Expand All @@ -11,6 +12,7 @@ export default {
config,
mount,
shallow,
renderToString,
TransitionStub,
TransitionGroupStub,
RouterLinkStub
Expand Down
3 changes: 3 additions & 0 deletions src/lib/add-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ function addSlots (vm: Component, slots: Object): void {

if (Array.isArray(slots[key])) {
slots[key].forEach((slotValue) => {
if (!isValidSlot(slotValue)) {
throwError('slots[key] must be a Component, string or an array of Components')
}
addSlotToVm(vm, key, slotValue)
})
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import errorHandler from './lib/error-handler'
import { findAllVueComponentsFromVm } from './lib/find-vue-components'

Vue.config.productionTip = false
Vue.config.errorHandler = errorHandler
Vue.config.devtools = false
Vue.config.errorHandler = errorHandler

export default function mount (component: Component, options: Options = {}): VueWrapper {
// Remove cached constructor
Expand Down
39 changes: 39 additions & 0 deletions src/renderToString.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// @flow

import Vue from 'vue'
import createInstance from './lib/create-instance'
import './lib/polyfills/object-assign-polyfill'
import { throwError } from './lib/util'

Vue.config.productionTip = false
Vue.config.devtools = false

export default function renderToString (component: Component, options: Options = {}): string {
let renderer
try {
renderer = require('vue-server-renderer').createRenderer()
} catch (e) {}

if (!renderer) {
throwError('renderToString must be run in node. It cannot be run in a browser')
}
// Remove cached constructor
delete component._Ctor

if (options.attachToDocument) {
throwError('you cannot use attachToDocument with renderToString')
}

const vm = createInstance(component, options)

let renderedString = ''

// $FlowIgnore
renderer.renderToString(vm, (err, res) => {
if (err) {
console.log(err)
}
renderedString = res
})
return renderedString
}
33 changes: 29 additions & 4 deletions test/resources/test-utils.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/* global describe, it*/

import Vue from 'vue'
import { shallow, mount } from '~vue-test-utils'
import { shallow, mount, renderToString } from '~vue-test-utils'

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

export const isRunningJSDOM = navigator.userAgent.includes && navigator.userAgent.includes('jsdom')

export function injectSupported () {
return vueVersion > 2.2
}
Expand All @@ -21,20 +23,43 @@ export function functionalSFCsSupported () {
return vueVersion >= 2.5
}

const shallowAndMount = [mount, shallow]
const shallowMountAndRender = isRunningJSDOM
? [mount, shallow, renderToString]
: [mount, shallow]

export function describeWithShallowAndMount (spec, cb) {
;[mount, shallow].forEach(method => {
shallowAndMount.forEach(method => {
describe(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithShallowAndMount.skip = function (spec, cb) {
;[mount, shallow].forEach(method => {
shallowAndMount.forEach(method => {
describe.skip(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithShallowAndMount.only = function (spec, cb) {
;[mount, shallow].forEach(method => {
shallowAndMount.forEach(method => {
describe.only(`${spec} with ${method.name}`, () => cb(method))
})
}

export function describeWithMountingMethods (spec, cb) {
shallowMountAndRender.forEach(method => {
describe(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithMountingMethods.skip = function (spec, cb) {
shallowMountAndRender.forEach(method => {
describe.skip(`${spec} with ${method.name}`, () => cb(method))
})
}

describeWithMountingMethods.only = function (spec, cb) {
shallowMountAndRender.forEach(method => {
describe.only(`${spec} with ${method.name}`, () => cb(method))
})
}
Expand Down
4 changes: 2 additions & 2 deletions test/setup/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ module.exports = function (config) {
reporters: ['spec'],
files: [
'../../node_modules/babel-polyfill/dist/polyfill.js',
'../specs/**/*.+(vue|js)'
'load-tests.js'
],
preprocessors: {
'../specs/**/*.+(vue|js)': ['webpack', 'sourcemap']
'load-tests.js': ['webpack', 'sourcemap']
},
client: { mocha: { timeout: 20000 }},
webpack: webpackConfig,
Expand Down
3 changes: 3 additions & 0 deletions test/setup/load-tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const testsContext = require.context('../specs', true, /\.spec\.(js|vue)$/)

testsContext.keys().forEach(testsContext)
5 changes: 4 additions & 1 deletion test/setup/webpack.test.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,8 @@ module.exports = {
devtoolModuleFilenameTemplate: '[absolute-resource-path]',
devtoolFallbackModuleFilenameTemplate: '[absolute-resource-path]?[hash]'
},
devtool: '#inline-cheap-module-source-map'
devtool: '#inline-cheap-module-source-map',
node: {
fs: 'empty'
}
}
1 change: 0 additions & 1 deletion test/specs/components/TransitionGroupStub.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ describe('TransitionGroupStub', () => {
}),
watch: {
someWatchedData (newData) {
console.log('asd')
this.someData = newData
}
},
Expand Down
19 changes: 18 additions & 1 deletion test/specs/mounting-options/attachToDocument.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { compileToFunctions } from 'vue-template-compiler'
import { describeWithShallowAndMount } from '~resources/test-utils'
import {
describeWithShallowAndMount,
isRunningJSDOM
} from '~resources/test-utils'
import { renderToString } from '~vue-test-utils'

describeWithShallowAndMount('options.attachToDocument', (mountingMethod) => {
it('returns VueWrapper with attachedToDocument set to true when passed attachToDocument in options', () => {
Expand All @@ -8,3 +12,16 @@ describeWithShallowAndMount('options.attachToDocument', (mountingMethod) => {
expect(wrapper.options.attachedToDocument).to.equal(true)
})
})

describe('options.attachToDocument with renderToString', () => {
it('throws error that renderToString does not accept attachToDocument', () => {
// renderToString can only be run in node
if (!isRunningJSDOM) {
return
}
const compiled = compileToFunctions('<div><input /></div>')
const fn = () => renderToString(compiled, { attachToDocument: true })
const message = '[vue-test-utils]: you cannot use attachToDocument with renderToString'
expect(fn).to.throw().with.property('message', message)
})
})
13 changes: 9 additions & 4 deletions test/specs/mounting-options/attrs.spec.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { compileToFunctions } from 'vue-template-compiler'
import { attrsSupported } from '~resources/test-utils'
import { describeWithShallowAndMount } from '~resources/test-utils'
import {
describeWithMountingMethods,
itSkipIf
} from '~resources/test-utils'

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

it('defines attrs as empty object even when not passed', () => {
itSkipIf(mountingMethod.name === 'renderToString',
'defines attrs as empty object even when not passed', () => {
const wrapper = mountingMethod(compileToFunctions('<p />'))
expect(wrapper.vm.$attrs).to.deep.equal({})
})
Expand Down
Loading

0 comments on commit da7aadc

Please sign in to comment.