diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index ea0cc8af3..515ddeff9 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -10,6 +10,7 @@ import extractInstanceOptions from './extract-instance-options' import createFunctionalComponent from './create-functional-component' import { componentNeedsCompiling } from 'shared/validators' import { validateSlots } from './validate-slots' +import createScopedSlots from './create-scoped-slots' export default function createInstance ( component: Component, @@ -122,6 +123,8 @@ export default function createInstance ( options.provide = () => obj } + const scopedSlots = createScopedSlots(options.scopedSlots) + const Parent = _Vue.extend({ provide: options.provide, render (h) { @@ -134,7 +137,8 @@ export default function createInstance ( ref: 'vm', props: options.propsData, on: options.listeners, - attrs: options.attrs + attrs: options.attrs, + scopedSlots }, slots ) diff --git a/packages/create-instance/create-scoped-slots.js b/packages/create-instance/create-scoped-slots.js new file mode 100644 index 000000000..6211fd320 --- /dev/null +++ b/packages/create-instance/create-scoped-slots.js @@ -0,0 +1,87 @@ +// @flow + +import Vue from 'vue' +import { compileToFunctions } from 'vue-template-compiler' +import { throwError, vueVersion } from 'shared/util' + +function isDestructuringSlotScope (slotScope: string): boolean { + return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}' +} + +function getVueTemplateCompilerHelpers (): { [name: string]: Function } { + const vue = new Vue() + const helpers = {} + const names = [ + '_c', + '_o', + '_n', + '_s', + '_l', + '_t', + '_q', + '_i', + '_m', + '_f', + '_k', + '_b', + '_v', + '_e', + '_u', + '_g' + ] + names.forEach(name => { + helpers[name] = vue._renderProxy[name] + }) + return helpers +} + +function validateEnvironment (): void { + if (window.navigator.userAgent.match(/PhantomJS/i)) { + throwError( + `the scopedSlots option does not support PhantomJS. ` + + `Please use Puppeteer, or pass a component.` + ) + } + if (vueVersion < 2.5) { + throwError(`the scopedSlots option is only supported in ` + `vue@2.5+.`) + } +} + +function validateTempldate (template: string): void { + if (template.trim().substr(0, 9) === ' VNode | Array} { + const scopedSlots = {} + if (!scopedSlotsOption) { + return scopedSlots + } + validateEnvironment() + const helpers = getVueTemplateCompilerHelpers() + for (const name in scopedSlotsOption) { + const template = scopedSlotsOption[name] + validateTempldate(template) + const render = compileToFunctions(template).render + const domParser = new window.DOMParser() + const _document = domParser.parseFromString(template, 'text/html') + const slotScope = _document.body.firstChild.getAttribute( + 'slot-scope' + ) + const isDestructuring = isDestructuringSlotScope(slotScope) + scopedSlots[name] = function (props) { + if (isDestructuring) { + return render.call({ ...helpers, ...props }) + } else { + return render.call({ ...helpers, [slotScope]: props }) + } + } + } + return scopedSlots +} diff --git a/packages/test-utils/src/add-scoped-slots.js b/packages/test-utils/src/add-scoped-slots.js deleted file mode 100644 index 2793371a7..000000000 --- a/packages/test-utils/src/add-scoped-slots.js +++ /dev/null @@ -1,92 +0,0 @@ -// @flow -import { compileToFunctions } from 'vue-template-compiler' -import { throwError, vueVersion } from 'shared/util' - -function isDestructuringSlotScope (slotScope: string): boolean { - return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}' -} - -function getVueTemplateCompilerHelpers (proxy: Object): Object { - const helpers = {} - const names = [ - '_c', - '_o', - '_n', - '_s', - '_l', - '_t', - '_q', - '_i', - '_m', - '_f', - '_k', - '_b', - '_v', - '_e', - '_u', - '_g' - ] - names.forEach(name => { - helpers[name] = proxy[name] - }) - return helpers -} - -export function addScopedSlots ( - vm: Component, - scopedSlots: { [name: string]: string } -): void { - if (window.navigator.userAgent.match(/PhantomJS/i)) { - throwError( - `the scopedSlots option does not support PhantomJS. ` + - `Please use Puppeteer, or pass a component.` - ) - } - - if (vueVersion < 2.5) { - throwError(`the scopedSlots option is only supported in ` + `vue@2.5+.`) - } - vm.$_vueTestUtils_scopedSlots = {} - vm.$_vueTestUtils_slotScopes = {} - const renderSlot = vm._renderProxy._t - - vm._renderProxy._t = function (name, feedback, props, bindObject) { - const scopedSlotFn = vm.$_vueTestUtils_scopedSlots[name] - const slotScope = vm.$_vueTestUtils_slotScopes[name] - if (scopedSlotFn) { - props = { ...bindObject, ...props } - const helpers = getVueTemplateCompilerHelpers(vm._renderProxy) - let proxy = { ...helpers } - if (isDestructuringSlotScope(slotScope)) { - proxy = { ...helpers, ...props } - } else { - proxy[slotScope] = props - } - return scopedSlotFn.call(proxy) - } else { - return renderSlot.call( - vm._renderProxy, - name, - feedback, - props, - bindObject - ) - } - } - - Object.keys(scopedSlots).forEach(key => { - const template = scopedSlots[key].trim() - if (template.substr(0, 9) === ' c._error ) diff --git a/test/specs/mounting-options/scopedSlots.spec.js b/test/specs/mounting-options/scopedSlots.spec.js index 64d70944a..05259800d 100644 --- a/test/specs/mounting-options/scopedSlots.spec.js +++ b/test/specs/mounting-options/scopedSlots.spec.js @@ -13,6 +13,48 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => { window = windowSave // eslint-disable-line no-native-reassign }) + itDoNotRunIf( + vueVersion < 2.5 || isRunningPhantomJS, + 'mounts component scoped slots in render function', + () => { + const destructuringWrapper = mountingMethod( + { + render: function () { + return this.$scopedSlots.default({ + index: 1, + item: 'foo' + }) + } + }, + { + scopedSlots: { + default: + '

{{index}},{{item}}

' + } + } + ) + expect(destructuringWrapper.html()).to.equal('

1,foo

') + + const notDestructuringWrapper = mountingMethod( + { + render: function () { + return this.$scopedSlots.default({ + index: 1, + item: 'foo' + }) + } + }, + { + scopedSlots: { + default: + '

{{props.index}},{{props.item}}

' + } + } + ) + expect(notDestructuringWrapper.html()).to.equal('

1,foo

') + } + ) + itDoNotRunIf( vueVersion < 2.5 || isRunningPhantomJS, 'mounts component scoped slots',