Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: improve slots mounting option #821

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/create-instance/create-functional-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/create-instance/create-instance.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
64 changes: 64 additions & 0 deletions packages/create-instance/create-render-slot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// @flow

import Vue from 'vue'
import { compileToFunctions } from 'vue-template-compiler'

const _renderSlot = Vue.prototype._t

function createVNodes (
vm: Component,
slotValue: Component | string
): ?Array<VNode> {
if (typeof slotValue === 'string') {
// Since compileToFunctions is checked in createSlotVNodes(),
// it is not necessary to check compileToFunctions.
const compiledResult = compileToFunctions(`<div>${slotValue}</div>`)
const _staticRenderFns = vm._renderProxy.$options.staticRenderFns
vm._renderProxy.$options.staticRenderFns = compiledResult.staticRenderFns
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you save the renderFunctions and then reassign?

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 (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of replacing the renderSlot alias? Does this solve the issue of parent being undefined?

options: Object
): (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object
) => ?Array<VNode> {
return function renderSlot (
name: string,
fallback: ?Array<VNode>,
props: ?Object,
bindObject: ?Object
): ?Array<VNode> {
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)
}
}
2 changes: 2 additions & 0 deletions packages/create-instance/create-scoped-slots.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Vue from 'vue'
import { compileToFunctions } from 'vue-template-compiler'
import { throwError, vueVersion } from 'shared/util'
import { checkCompileToFunctions } from 'shared/validators'

function isDestructuringSlotScope (slotScope: string): boolean {
return slotScope[0] === '{' && slotScope[slotScope.length - 1] === '}'
Expand Down Expand Up @@ -36,6 +37,7 @@ function getVueTemplateCompilerHelpers (): { [name: string]: Function } {
}

function validateEnvironment (): void {
checkCompileToFunctions()
if (window.navigator.userAgent.match(/PhantomJS/i)) {
throwError(
`the scopedSlots option does not support PhantomJS. ` +
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @flow

import { compileToFunctions } from 'vue-template-compiler'
import { checkCompileToFunctions } from 'shared/validators'

function startsWithTag (str: SlotValue): boolean {
return typeof str === 'string' && str.trim()[0] === '<'
Expand All @@ -15,6 +16,7 @@ function createVNodesForSlot (
return slotValue
}

checkCompileToFunctions()
const el =
typeof slotValue === 'string' ? compileToFunctions(slotValue) : slotValue

Expand Down
6 changes: 6 additions & 0 deletions packages/create-instance/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// @flow

import createInstance from './create-instance'
import createRenderSlot from './create-render-slot'

export { createInstance, createRenderSlot }
2 changes: 1 addition & 1 deletion packages/create-instance/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "create-instance",
"version": "1.0.0-beta.20",
"main": "create-instance.js",
"main": "index.js",
"private": true
}
20 changes: 7 additions & 13 deletions packages/create-instance/validate-slots.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// @flow

import { throwError } from 'shared/util'
import { compileToFunctions } from 'vue-template-compiler'
import { isVueComponent } from '../shared/validators'
import {
checkCompileToFunctions,
isVueComponent
} from 'shared/validators'

function isValidSlot (slot: any): boolean {
return (
Expand All @@ -11,16 +13,6 @@ function isValidSlot (slot: any): boolean {
)
}

function requiresTemplateCompiler (slot: any): void {
if (typeof slot === 'string' && !compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}
}

export function validateSlots (slots: SlotsObject): void {
Object.keys(slots).forEach(key => {
const slot = Array.isArray(slots[key]) ? slots[key] : [slots[key]]
Expand All @@ -32,7 +24,9 @@ export function validateSlots (slots: SlotsObject): void {
`of Components`
)
}
requiresTemplateCompiler(slotValue)
if (typeof slotValue === 'string') {
checkCompileToFunctions()
}
})
})
}
4 changes: 3 additions & 1 deletion packages/server-test-utils/src/renderToString.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/compile-template.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// @flow

import { compileToFunctions } from 'vue-template-compiler'
import { checkCompileToFunctions } from './validators'

export function compileTemplate (component: Component): void {
if (component.template) {
checkCompileToFunctions()
Object.assign(component, compileToFunctions(component.template))
}

Expand Down
17 changes: 3 additions & 14 deletions packages/shared/stub-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
hyphenate
} from './util'
import {
checkCompileToFunctions,
componentNeedsCompiling,
templateContainsComponent,
isVueComponent
Expand Down Expand Up @@ -66,13 +67,7 @@ function createStubFromString (
originalComponent: Component,
name: string
): Component {
if (!compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}
checkCompileToFunctions()

if (templateContainsComponent(templateString, name)) {
throwError('options.stub cannot contain a circular reference')
Expand Down Expand Up @@ -176,13 +171,7 @@ export function createComponentStubs (
}
} else {
if (typeof stub === 'string') {
if (!compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}
checkCompileToFunctions()
components[stubName] = {
...compileToFunctions(stub)
}
Expand Down
11 changes: 11 additions & 0 deletions packages/shared/validators.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
// @flow
import { compileToFunctions } from 'vue-template-compiler'
import { throwError, capitalize, camelize, hyphenate } from './util'

export function checkCompileToFunctions (): void {
if (!compileToFunctions) {
throwError(
`vueTemplateCompiler is undefined, you must pass ` +
`precompiled components if vue-template-compiler is ` +
`undefined`
)
}
}

export function isDomSelector (selector: any): boolean {
if (typeof selector !== 'string') {
return false
Expand Down
3 changes: 2 additions & 1 deletion packages/test-utils/src/mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ 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'
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
Expand All @@ -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

Expand Down
18 changes: 18 additions & 0 deletions test/resources/components/component-with-parent-name.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<template>
<div><span baz="qux">{{ time }},{{ fromLocalVue }},{{ bar }}</span></div>
</template>

<script>
export default{
name: 'component-with-parent-name',
props: ['fromLocalVue', 'time'],
data () {
return {
bar: 'quux'
}
},
mounted () {
this.$parent.childComponentName = this.$options.name
}
}
</script>
41 changes: 40 additions & 1 deletion test/specs/mounting-options/scopedSlots.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
isRunningPhantomJS
} from '~resources/utils'
import ComponentWithScopedSlots from '~resources/components/component-with-scoped-slots.vue'
import { itDoNotRunIf } from 'conditional-specs'
import { itSkipIf, itDoNotRunIf } from 'conditional-specs'

describeWithShallowAndMount('scopedSlots', mountingMethod => {
const windowSave = window
Expand Down Expand Up @@ -165,4 +165,43 @@ describeWithShallowAndMount('scopedSlots', mountingMethod => {
.with.property('message', message)
}
)

itSkipIf(
mountingMethod.name === 'renderToString',
'throws error if passed string in default slot object and vue-template-compiler is undefined',
() => {
const compilerSave =
require.cache[require.resolve('vue-template-compiler')].exports
.compileToFunctions
require.cache[
require.resolve('vue-template-compiler')
].exports.compileToFunctions = undefined
delete require.cache[require.resolve('../../../packages/test-utils')]
const mountingMethodFresh = require('../../../packages/test-utils')[
mountingMethod.name
]
const message =
'[vue-test-utils]: vueTemplateCompiler is undefined, you must pass precompiled components if vue-template-compiler is undefined'
const fn = () => {
mountingMethodFresh(ComponentWithScopedSlots, {
scopedSlots: {
list: '<p slot-scope="foo">{{foo.index}},{{foo.text}}</p>'
}
})
}
try {
expect(fn)
.to.throw()
.with.property('message', message)
} catch (err) {
require.cache[
require.resolve('vue-template-compiler')
].exports.compileToFunctions = compilerSave
throw err
}
require.cache[
require.resolve('vue-template-compiler')
].exports.compileToFunctions = compilerSave
}
)
})
Loading