From 64a8be250bb6df353813c48cfe7cc0b6c70b2a55 Mon Sep 17 00:00:00 2001 From: 38elements Date: Thu, 12 Jul 2018 21:47:05 +0900 Subject: [PATCH 1/4] fix: improve slots mounting option --- .../create-functional-component.js | 2 +- packages/create-instance/create-instance.js | 2 +- .../create-instance/create-render-slot.js | 62 ++++++++++++++++ .../{add-slots.js => create-slot-vnodes.js} | 0 packages/create-instance/index.js | 6 ++ packages/create-instance/package.json | 2 +- .../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 | 70 +++++++++++++++++-- 10 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 packages/create-instance/create-render-slot.js rename packages/create-instance/{add-slots.js => create-slot-vnodes.js} (100%) create mode 100644 packages/create-instance/index.js 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..2d7437d3e 100644 --- a/packages/create-instance/create-instance.js +++ b/packages/create-instance/create-instance.js @@ -1,9 +1,9 @@ // @flow -import { createSlotVNodes } from './add-slots' import addMocks from './add-mocks' import { addEventLogger } from './log-events' import { createComponentStubs } from 'shared/stub-components' +import { createSlotVNodes } from './create-slot-vnodes' import { throwError, warn, vueVersion } from 'shared/util' import { compileTemplate } from 'shared/compile-template' import extractInstanceOptions from './extract-instance-options' diff --git a/packages/create-instance/create-render-slot.js b/packages/create-instance/create-render-slot.js new file mode 100644 index 000000000..6e7d750f7 --- /dev/null +++ b/packages/create-instance/create-render-slot.js @@ -0,0 +1,62 @@ +// @flow + +import Vue from 'vue' +import { compileToFunctions } from 'vue-template-compiler' + +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 vnodes = compiledResult.render.call( + vm._renderProxy, vm.$createElement + ).children + vm._renderProxy.$options.staticRenderFns = _staticRenderFns + return vnodes + } + return [vm.$createElement(slotValue)] +} + +export default 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 (options.slots && options.slots[name]) { + this.$slots[name] = [] + const slotsValue = options.slots[name] + if (Array.isArray(slotsValue)) { + slotsValue.forEach((value) => { + if (typeof value === 'string') { + const vnodes = createVNodes(this, value) + if (Array.isArray(vnodes)) { + this.$slots[name].push(...vnodes) + } + } else { + this.$slots[name].push(this.$createElement(value)) + } + }) + } else { + const vnodes = createVNodes(this, slotsValue) + if (Array.isArray(vnodes)) { + this.$slots[name] = vnodes + } + } + } + return _renderSlot.call(this, name, fallback, props, bindObject) + } +} 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/create-instance/index.js b/packages/create-instance/index.js new file mode 100644 index 000000000..f03af3c0a --- /dev/null +++ b/packages/create-instance/index.js @@ -0,0 +1,6 @@ +// @flow + +import createInstance from './create-instance' +import createRenderSlot from './create-render-slot' + +export { createInstance, createRenderSlot } diff --git a/packages/create-instance/package.json b/packages/create-instance/package.json index a9cecc559..c15e81695 100644 --- a/packages/create-instance/package.json +++ b/packages/create-instance/package.json @@ -1,6 +1,6 @@ { "name": "create-instance", "version": "1.0.0-beta.20", - "main": "create-instance.js", + "main": "index.js", "private": true } 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 19e338b05..75c9adb55 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,18 @@ 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') + expect(wrapper).contains( + '
hello,
world
' + ) } else { - expect(wrapper.text()).to.contain('hello,world') + expect(wrapper.html()).to.equal( + '
hello,
world
' + ) } }) @@ -568,4 +574,60 @@ describeWithMountingMethods('options.slots', mountingMethod => { expect(wrapper.contains(ComponentAsAClass)).to.equal(true) } }) + + itDoNotRunIf( + mountingMethod.name === 'renderToString', + 'mounts component with default slot if passed string in slot object', + () => { + const wrapper1 = mount(ComponentWithSlots, { slots: { default: 'foo123{{ foo }}' }}) + expect(wrapper1.find('main').html()).to.equal('
foo123bar
') + const wrapper2 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}2' }}) + expect(wrapper2.find('main').html()).to.equal('

1

bar2
') + const wrapper3 = mount(ComponentWithSlots, { slots: { default: '

1

{{ foo }}

2

' }}) + expect(wrapper3.find('main').html()).to.equal('

1

bar

2

') + const wrapper4 = mount(ComponentWithSlots, { slots: { default: '123' }}) + expect(wrapper4.find('main').html()).to.equal('
123
') + const wrapper5 = mount(ComponentWithSlots, { slots: { default: '1{{ foo }}2' }}) + expect(wrapper5.find('main').html()).to.equal('
1bar2
') + wrapper5.trigger('keydown') + expect(wrapper5.find('main').html()).to.equal('
1BAR2
') + const wrapper6 = mount(ComponentWithSlots, { slots: { default: '

1

2

' }}) + expect(wrapper6.find('main').html()).to.equal('

1

2

') + const wrapper7 = mount(ComponentWithSlots, { slots: { default: '1

2

3' }}) + expect(wrapper7.find('main').html()).to.equal('
1

2

3
') + const wrapper8 = mountingMethod(ComponentWithSlots, { slots: { default: ' space ' }}) + expect(wrapper8.find('main').html()).to.equal('
space
') + } + ) + + 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
') + } + ) }) From d0e7bdf9b8f779e476b53e9d5b117f7e4379e73b Mon Sep 17 00:00:00 2001 From: 38elements Date: Mon, 16 Jul 2018 14:29:13 +0900 Subject: [PATCH 2/4] improve test --- .../components/component-with-parent-name.vue | 9 +++---- test/specs/mounting-options/slots.spec.js | 24 ++++++++++++++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/test/resources/components/component-with-parent-name.vue b/test/resources/components/component-with-parent-name.vue index eb46358b6..a73b91c25 100644 --- a/test/resources/components/component-with-parent-name.vue +++ b/test/resources/components/component-with-parent-name.vue @@ -1,17 +1,18 @@ -i