From c24e8c59d0cb4ad6d61af82c926151e00e65154d Mon Sep 17 00:00:00 2001 From: 38elements Date: Thu, 12 Jul 2018 21:47:05 +0900 Subject: [PATCH] fix: improve slots mounting option --- .../create-functional-component.js | 2 +- packages/create-instance/create-instance.js | 64 ++++++++++++++++++- .../{add-slots.js => create-slot-vnodes.js} | 0 .../server-test-utils/src/renderToString.js | 4 +- packages/test-utils/src/mount.js | 3 +- .../components/component-with-parent-name.vue | 17 +++++ test/specs/mounting-options/slots.spec.js | 40 +++++++++++- 7 files changed, 122 insertions(+), 8 deletions(-) rename packages/create-instance/{add-slots.js => create-slot-vnodes.js} (100%) create mode 100644 test/resources/components/component-with-parent-name.vue diff --git a/packages/create-instance/create-functional-component.js b/packages/create-instance/create-functional-component.js index ee3feb392..04021ba23 100644 --- a/packages/create-instance/create-functional-component.js +++ b/packages/create-instance/create-functional-component.js @@ -2,7 +2,7 @@ import { throwError } from 'shared/util' import { validateSlots } from './validate-slots' -import { createSlotVNodes } from './add-slots' +import { createSlotVNodes } from './create-slot-vnodes' export default function createFunctionalComponent ( component: Component, diff --git a/packages/create-instance/create-instance.js b/packages/create-instance/create-instance.js index 515ddeff9..b01a186e5 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -1,6 +1,8 @@ // @flow -import { createSlotVNodes } from './add-slots' +import Vue from 'vue' +import { compileToFunctions } from 'vue-template-compiler' +import { createSlotVNodes } from './create-slot-vnodes' import addMocks from './add-mocks' import { addEventLogger } from './log-events' import { createComponentStubs } from 'shared/stub-components' @@ -12,7 +14,65 @@ import { componentNeedsCompiling } from 'shared/validators' import { validateSlots } from './validate-slots' import createScopedSlots from './create-scoped-slots' -export default function createInstance ( +const _renderSlot = Vue.prototype._t + +function createVNodes ( + vm: Component, + slotValue: Component | string +): Array { + if (typeof slotValue === 'string') { + const compiledResult = compileToFunctions(`
${slotValue}{{ }}
`) + const _staticRenderFns = vm._renderProxy.$options.staticRenderFns + vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns + const elem = compiledResult.render.call( + vm._renderProxy, vm.$createElement + ).children + vm._renderProxy.$options.staticRenderFns = _staticRenderFns + return elem + } + return [vm.$createElement(slotValue)] +} + +export function createRenderSlot ( + options: Object +): ( + name: string, + fallback: ?Array, + props: ?Object, + bindObject: ?Object +) => ?Array { + return function renderSlot ( + name: string, + fallback: ?Array, + props: ?Object, + bindObject: ?Object + ): ?Array { + if (createVNodes && options.slots && options.slots[name]) { + const slotsValue = options.slots[name] + if (Array.isArray(slotsValue)) { + this.$slots[name] = [] + for (let i = 0; i < slotsValue.length; i++) { + const _slotValue = slotsValue[i] + if (typeof _slotValue === 'string') { + const slots = createVNodes(this, _slotValue) + if (Array.isArray(slots)) { + this.$slots[name].push(...slots) + } else { + this.$slots[name].push(slots) + } + } else { + this.$slots[name].push(this.$createElement(_slotValue)) + } + } + } else { + this.$slots[name] = createVNodes(this, slotsValue) + } + } + return _renderSlot.call(this, name, fallback, props, bindObject) + } +} + +export function createInstance ( component: Component, options: Options, _Vue: Component, diff --git a/packages/create-instance/add-slots.js b/packages/create-instance/create-slot-vnodes.js similarity index 100% rename from packages/create-instance/add-slots.js rename to packages/create-instance/create-slot-vnodes.js diff --git a/packages/server-test-utils/src/renderToString.js b/packages/server-test-utils/src/renderToString.js index cfdd8ab7e..55a5f4b25 100644 --- a/packages/server-test-utils/src/renderToString.js +++ b/packages/server-test-utils/src/renderToString.js @@ -1,7 +1,7 @@ // @flow import Vue from 'vue' -import createInstance from 'create-instance' +import { createInstance, createRenderSlot } from 'create-instance' import { throwError } from 'shared/util' import { createRenderer } from 'vue-server-renderer' import testUtils from '@vue/test-utils' @@ -29,6 +29,8 @@ export default function renderToString ( throwError(`you cannot use attachToDocument with ` + `renderToString`) } const vueConstructor = testUtils.createLocalVue(options.localVue) + vueConstructor.prototype._t = createRenderSlot(options) + const vm = createInstance( component, mergeOptions(options, config), diff --git a/packages/test-utils/src/mount.js b/packages/test-utils/src/mount.js index 52638d248..99db619da 100644 --- a/packages/test-utils/src/mount.js +++ b/packages/test-utils/src/mount.js @@ -4,7 +4,6 @@ import './matches-polyfill' import './object-assign-polyfill' import Vue from 'vue' import VueWrapper from './vue-wrapper' -import createInstance from 'create-instance' import createElement from './create-element' import createLocalVue from './create-local-vue' import errorHandler from './error-handler' @@ -12,6 +11,7 @@ import { findAllVueComponentsFromVm } from './find-vue-components' import { mergeOptions } from 'shared/merge-options' import config from './config' import warnIfNoWindow from './warn-if-no-window' +import { createInstance, createRenderSlot } from 'create-instance' Vue.config.productionTip = false Vue.config.devtools = false @@ -28,6 +28,7 @@ export default function mount ( // Remove cached constructor delete component._Ctor const vueConstructor = createLocalVue(options.localVue) + vueConstructor.prototype._t = createRenderSlot(options) const elm = options.attachToDocument ? createElement() : undefined diff --git a/test/resources/components/component-with-parent-name.vue b/test/resources/components/component-with-parent-name.vue new file mode 100644 index 000000000..eb46358b6 --- /dev/null +++ b/test/resources/components/component-with-parent-name.vue @@ -0,0 +1,17 @@ +i + + diff --git a/test/specs/mounting-options/slots.spec.js b/test/specs/mounting-options/slots.spec.js index d0e769312..b8351e5fd 100644 --- a/test/specs/mounting-options/slots.spec.js +++ b/test/specs/mounting-options/slots.spec.js @@ -2,8 +2,10 @@ import { compileToFunctions } from 'vue-template-compiler' import Component from '~resources/components/component.vue' import ComponentWithSlots from '~resources/components/component-with-slots.vue' import ComponentAsAClass from '~resources/components/component-as-a-class.vue' +import ComponentWithParentName from '~resources/components/component-with-parent-name.vue' import { describeWithMountingMethods, vueVersion } from '~resources/utils' import { itSkipIf, itDoNotRunIf } from 'conditional-specs' +import { mount, createLocalVue } from '~vue/test-utils' describeWithMountingMethods('options.slots', mountingMethod => { it('mounts component with default slot if passed component in slot object', () => { @@ -224,14 +226,15 @@ describeWithMountingMethods('options.slots', mountingMethod => { it('mounts component with text slot', () => { const wrapper = mountingMethod(ComponentWithSlots, { slots: { - default: 'hello,', - header: 'world' + header: 'hello,', + default: 'world' } }) if (mountingMethod.name === 'renderToString') { expect(wrapper).contains('hello,world') } else { - expect(wrapper.text()).to.contain('hello,world') + expect(wrapper.find('header').text()).to.equal('hello,') + expect(wrapper.find('main').text()).to.equal('world') } }) @@ -546,4 +549,35 @@ describeWithMountingMethods('options.slots', mountingMethod => { wrapper.find('div').trigger('click') } ) + + itDoNotRunIf( + mountingMethod.name === 'renderToString', + 'sets a component which can access the parent component', + () => { + const localVue = createLocalVue() + localVue.prototype.bar = 'FOO' + const wrapperComponent = mount( + { + name: 'parentComponent', + template: '
', + data () { + return { + childName: '' + } + } + }, + { + components: { + ComponentWithParentName + }, + slots: { + default: '' + }, + localVue + } + ) + expect(wrapperComponent.vm.childName).to.equal('component-with-parent-name') + expect(wrapperComponent.html()).to.equal('
quux
') + } + ) })