mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
5e52ac9ab4
|
@ -408,4 +408,22 @@ describe('compiler: transform v-bind', () => {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('error on invalid argument for same-name shorthand', () => {
|
||||||
|
const onError = vi.fn()
|
||||||
|
parseWithVBind(`<div v-bind:[arg] />`, { onError })
|
||||||
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
|
code: ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
|
||||||
|
loc: {
|
||||||
|
start: {
|
||||||
|
line: 1,
|
||||||
|
column: 13,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: 1,
|
||||||
|
column: 18,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -78,6 +78,7 @@ export enum ErrorCodes {
|
||||||
X_V_FOR_MALFORMED_EXPRESSION,
|
X_V_FOR_MALFORMED_EXPRESSION,
|
||||||
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
|
X_V_FOR_TEMPLATE_KEY_PLACEMENT,
|
||||||
X_V_BIND_NO_EXPRESSION,
|
X_V_BIND_NO_EXPRESSION,
|
||||||
|
X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
|
||||||
X_V_ON_NO_EXPRESSION,
|
X_V_ON_NO_EXPRESSION,
|
||||||
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
|
||||||
X_V_SLOT_MIXED_SLOT_USAGE,
|
X_V_SLOT_MIXED_SLOT_USAGE,
|
||||||
|
@ -156,6 +157,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
|
||||||
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
|
[ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`,
|
||||||
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
|
[ErrorCodes.X_V_FOR_TEMPLATE_KEY_PLACEMENT]: `<template v-for> key should be placed on the <template> tag.`,
|
||||||
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
[ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||||
|
[ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT]: `v-bind with same-name shorthand only allows static argument.`,
|
||||||
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
[ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
||||||
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
[ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
|
||||||
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
|
[ErrorCodes.X_V_SLOT_MIXED_SLOT_USAGE]:
|
||||||
|
|
|
@ -2,6 +2,7 @@ import type { DirectiveTransform } from '../transform'
|
||||||
import {
|
import {
|
||||||
type ExpressionNode,
|
type ExpressionNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
|
type SimpleExpressionNode,
|
||||||
createObjectProperty,
|
createObjectProperty,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
@ -17,10 +18,45 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
const { modifiers, loc } = dir
|
const { modifiers, loc } = dir
|
||||||
const arg = dir.arg!
|
const arg = dir.arg!
|
||||||
|
|
||||||
// :arg is replaced by :arg="arg"
|
|
||||||
let { exp } = dir
|
let { exp } = dir
|
||||||
if (!exp && arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
|
||||||
const propName = camelize(arg.content)
|
// handle empty expression
|
||||||
|
if (exp && exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) {
|
||||||
|
if (!__BROWSER__) {
|
||||||
|
// #10280 only error against empty expression in non-browser build
|
||||||
|
// because :foo in in-DOM templates will be parsed into :foo="" by the
|
||||||
|
// browser
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
props: [
|
||||||
|
createObjectProperty(arg, createSimpleExpression('', true, loc)),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exp = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// same-name shorthand - :arg is expanded to :arg="arg"
|
||||||
|
if (!exp) {
|
||||||
|
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
|
||||||
|
// only simple expression is allowed for same-name shorthand
|
||||||
|
context.onError(
|
||||||
|
createCompilerError(
|
||||||
|
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
|
||||||
|
arg.loc,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
props: [
|
||||||
|
createObjectProperty(arg, createSimpleExpression('', true, loc)),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||||
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
|
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||||
if (!__BROWSER__) {
|
if (!__BROWSER__) {
|
||||||
exp = dir.exp = processExpression(exp, context)
|
exp = dir.exp = processExpression(exp, context)
|
||||||
|
@ -57,16 +93,6 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
!exp ||
|
|
||||||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
|
|
||||||
) {
|
|
||||||
context.onError(createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc))
|
|
||||||
return {
|
|
||||||
props: [createObjectProperty(arg, createSimpleExpression('', true, loc))],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: [createObjectProperty(arg, exp)],
|
props: [createObjectProperty(arg, exp)],
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,6 +123,13 @@ describe('api: createApp', () => {
|
||||||
|
|
||||||
expect(app.runWithContext(() => inject('foo'))).toBe(1)
|
expect(app.runWithContext(() => inject('foo'))).toBe(1)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
app.runWithContext(() => {
|
||||||
|
app.runWithContext(() => {})
|
||||||
|
return inject('foo')
|
||||||
|
}),
|
||||||
|
).toBe(1)
|
||||||
|
|
||||||
// ensure the context is restored
|
// ensure the context is restored
|
||||||
inject('foo')
|
inject('foo')
|
||||||
expect('inject() can only be used inside setup').toHaveBeenWarned()
|
expect('inject() can only be used inside setup').toHaveBeenWarned()
|
||||||
|
|
|
@ -386,11 +386,12 @@ export function createAppAPI<HostElement>(
|
||||||
},
|
},
|
||||||
|
|
||||||
runWithContext(fn) {
|
runWithContext(fn) {
|
||||||
|
const lastApp = currentApp
|
||||||
currentApp = app
|
currentApp = app
|
||||||
try {
|
try {
|
||||||
return fn()
|
return fn()
|
||||||
} finally {
|
} finally {
|
||||||
currentApp = null
|
currentApp = lastApp
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -81,10 +81,9 @@ export function setRef(
|
||||||
} else {
|
} else {
|
||||||
const _isString = isString(ref)
|
const _isString = isString(ref)
|
||||||
const _isRef = isRef(ref)
|
const _isRef = isRef(ref)
|
||||||
const isVFor = rawRef.f
|
|
||||||
if (_isString || _isRef) {
|
if (_isString || _isRef) {
|
||||||
const doSet = () => {
|
const doSet = () => {
|
||||||
if (isVFor) {
|
if (rawRef.f) {
|
||||||
const existing = _isString
|
const existing = _isString
|
||||||
? hasOwn(setupState, ref)
|
? hasOwn(setupState, ref)
|
||||||
? setupState[ref]
|
? setupState[ref]
|
||||||
|
@ -119,15 +118,14 @@ export function setRef(
|
||||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #9908 ref on v-for mutates the same array for both mount and unmount
|
if (value) {
|
||||||
// and should be done together
|
// #1789: for non-null values, set them after render
|
||||||
if (isUnmount || isVFor) {
|
// null values means this is unmount and it should not overwrite another
|
||||||
doSet()
|
// ref with the same key
|
||||||
} else {
|
|
||||||
// #1789: set new refs in a post job so that they don't get overwritten
|
|
||||||
// by unmounting ones.
|
|
||||||
;(doSet as SchedulerJob).id = -1
|
;(doSet as SchedulerJob).id = -1
|
||||||
queuePostRenderEffect(doSet, parentSuspense)
|
queuePostRenderEffect(doSet, parentSuspense)
|
||||||
|
} else {
|
||||||
|
doSet()
|
||||||
}
|
}
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||||
|
|
|
@ -211,4 +211,69 @@ describe('runtime-dom: v-show directive', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect($div.style.display).toEqual('')
|
expect($div.style.display).toEqual('')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10151
|
||||||
|
test('should respect the display value when v-show value is true', async () => {
|
||||||
|
const isVisible = ref(false)
|
||||||
|
const useDisplayStyle = ref(true)
|
||||||
|
const compStyle = ref({
|
||||||
|
display: 'none',
|
||||||
|
})
|
||||||
|
const withoutDisplayStyle = {
|
||||||
|
margin: '10px',
|
||||||
|
}
|
||||||
|
|
||||||
|
const Component = {
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
return withVShow(
|
||||||
|
h('div', {
|
||||||
|
style: useDisplayStyle.value
|
||||||
|
? compStyle.value
|
||||||
|
: withoutDisplayStyle,
|
||||||
|
}),
|
||||||
|
isVisible.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(Component), root)
|
||||||
|
|
||||||
|
const $div = root.children[0]
|
||||||
|
|
||||||
|
expect($div.style.display).toEqual('none')
|
||||||
|
|
||||||
|
isVisible.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('none')
|
||||||
|
|
||||||
|
compStyle.value.display = 'block'
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('block')
|
||||||
|
|
||||||
|
compStyle.value.display = 'inline-block'
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('inline-block')
|
||||||
|
|
||||||
|
isVisible.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('none')
|
||||||
|
|
||||||
|
isVisible.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('inline-block')
|
||||||
|
|
||||||
|
useDisplayStyle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('')
|
||||||
|
expect(getComputedStyle($div).display).toEqual('block')
|
||||||
|
|
||||||
|
isVisible.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('none')
|
||||||
|
|
||||||
|
isVisible.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect($div.style.display).toEqual('')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated(el, { value, oldValue }, { transition }) {
|
updated(el, { value, oldValue }, { transition }) {
|
||||||
if (!value === !oldValue) return
|
if (!value === !oldValue && el.style.display === el[vShowOldKey]) return
|
||||||
if (transition) {
|
if (transition) {
|
||||||
if (value) {
|
if (value) {
|
||||||
transition.beforeEnter(el)
|
transition.beforeEnter(el)
|
||||||
|
|
|
@ -38,6 +38,7 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
|
||||||
// so we always keep the current `display` value regardless of the `style`
|
// so we always keep the current `display` value regardless of the `style`
|
||||||
// value, thus handing over control to `v-show`.
|
// value, thus handing over control to `v-show`.
|
||||||
if (vShowOldKey in el) {
|
if (vShowOldKey in el) {
|
||||||
|
el[vShowOldKey] = style.display
|
||||||
style.display = currentDisplay
|
style.display = currentDisplay
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue