From 7b7e40ed899dd35bda506a3d6a08e3bc9717e7e1 Mon Sep 17 00:00:00 2001 From: 38elements Date: Thu, 5 Jul 2018 23:39:09 +0900 Subject: [PATCH 1/3] fix: improve scopedSlots option --- packages/create-instance/create-instance.js | 6 +- packages/create-instance/get-scoped-slots.js | 87 ++++++++++++++++++ packages/test-utils/src/add-scoped-slots.js | 92 ------------------- packages/test-utils/src/mount.js | 11 --- .../mounting-options/scopedSlots.spec.js | 24 +++++ 5 files changed, 116 insertions(+), 104 deletions(-) create mode 100644 packages/create-instance/get-scoped-slots.js delete mode 100644 packages/test-utils/src/add-scoped-slots.js diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index ea0cc8af3..6b6fbddb9 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 getScopedSlots from './get-scoped-slots' export default function createInstance ( component: Component, @@ -122,6 +123,8 @@ export default function createInstance ( options.provide = () => obj } + const scopedSlots = getScopedSlots(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/get-scoped-slots.js b/packages/create-instance/get-scoped-slots.js new file mode 100644 index 000000000..c8858ea2c --- /dev/null +++ b/packages/create-instance/get-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 } { + 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..b6bed1599 100644 --- a/test/specs/mounting-options/scopedSlots.spec.js +++ b/test/specs/mounting-options/scopedSlots.spec.js @@ -13,6 +13,30 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => { window = windowSave // eslint-disable-line no-native-reassign }) + itDoNotRunIf( + vueVersion < 2.5 || isRunningPhantomJS, + 'mounts component scoped slots in render function', + () => { + const wrapper = mountingMethod( + { + render: function () { + return this.$scopedSlots.default({ + index: 1, + item: 'foo' + }) + } + }, + { + scopedSlots: { + default: + '

{{index}},{{item}}

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

1,foo

') + } + ) + itDoNotRunIf( vueVersion < 2.5 || isRunningPhantomJS, 'mounts component scoped slots', From e268e2919f8b0d5b4c37efe469ecec278c2e040a Mon Sep 17 00:00:00 2001 From: 38elements Date: Fri, 6 Jul 2018 21:37:13 +0900 Subject: [PATCH 2/3] rename getScopedSlots to createScopedSlots --- packages/create-instance/create-instance.js | 4 ++-- ...scoped-slots.js => create-scoped-slots.js} | 2 +- .../mounting-options/scopedSlots.spec.js | 22 +++++++++++++++++-- 3 files changed, 23 insertions(+), 5 deletions(-) rename packages/create-instance/{get-scoped-slots.js => create-scoped-slots.js} (98%) diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 6b6fbddb9..515ddeff9 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -10,7 +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 getScopedSlots from './get-scoped-slots' +import createScopedSlots from './create-scoped-slots' export default function createInstance ( component: Component, @@ -123,7 +123,7 @@ export default function createInstance ( options.provide = () => obj } - const scopedSlots = getScopedSlots(options.scopedSlots) + const scopedSlots = createScopedSlots(options.scopedSlots) const Parent = _Vue.extend({ provide: options.provide, diff --git a/packages/create-instance/get-scoped-slots.js b/packages/create-instance/create-scoped-slots.js similarity index 98% rename from packages/create-instance/get-scoped-slots.js rename to packages/create-instance/create-scoped-slots.js index c8858ea2c..19a7f236b 100644 --- a/packages/create-instance/get-scoped-slots.js +++ b/packages/create-instance/create-scoped-slots.js @@ -56,7 +56,7 @@ function validateTempldate (template: string): void { } } -export default function getScopedSlots ( +export default function createScopedSlots ( scopedSlotsOption: ?{ [slotName: string]: string } ): { [slotName: string]: (props: Object) => VNode } { const scopedSlots = {} diff --git a/test/specs/mounting-options/scopedSlots.spec.js b/test/specs/mounting-options/scopedSlots.spec.js index b6bed1599..05259800d 100644 --- a/test/specs/mounting-options/scopedSlots.spec.js +++ b/test/specs/mounting-options/scopedSlots.spec.js @@ -17,7 +17,7 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => { vueVersion < 2.5 || isRunningPhantomJS, 'mounts component scoped slots in render function', () => { - const wrapper = mountingMethod( + const destructuringWrapper = mountingMethod( { render: function () { return this.$scopedSlots.default({ @@ -33,7 +33,25 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => { } } ) - expect(wrapper.html()).to.equal('

1,foo

') + 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

') } ) From ebf791884feca14c3af84c5b0050361249529c50 Mon Sep 17 00:00:00 2001 From: 38elements Date: Fri, 6 Jul 2018 22:28:00 +0900 Subject: [PATCH 3/3] fix type --- packages/create-instance/create-scoped-slots.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-instance/create-scoped-slots.js b/packages/create-instance/create-scoped-slots.js index 19a7f236b..6211fd320 100644 --- a/packages/create-instance/create-scoped-slots.js +++ b/packages/create-instance/create-scoped-slots.js @@ -58,7 +58,7 @@ function validateTempldate (template: string): void { export default function createScopedSlots ( scopedSlotsOption: ?{ [slotName: string]: string } -): { [slotName: string]: (props: Object) => VNode } { +): { [slotName: string]: (props: Object) => VNode | Array} { const scopedSlots = {} if (!scopedSlotsOption) { return scopedSlots