diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 9280aee88dd..18b5d90eaf6 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -44,21 +44,6 @@ return { a }
})"
`;
-exports[`SFC compile
- `)
- // should generate working code
- assertCode(content)
- // should analyze bindings
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.LITERAL_CONST,
- props: BindingTypes.SETUP_REACTIVE_CONST
- })
-
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineProps')
- // should generate correct setup signature
- expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
- // should assign user identifier to it
- expect(content).toMatch(`const props = __props`)
- // should include context options in default export
- expect(content).toMatch(`export default {
- props: {
- foo: String
-},`)
- })
-
- test('defineProps w/ external definition', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`export default {
- props: propsModel,`)
- })
-
- // #4764
- test('defineProps w/ leading code', () => {
- const { content } = compile(`
-
- `)
- // props declaration should be inside setup, not moved along with the import
- expect(content).not.toMatch(`const props = __props\nimport`)
- assertCode(content)
- })
-
- test('defineEmits()', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(bindings).toStrictEqual({
- myEmit: BindingTypes.SETUP_CONST
- })
- // should remove defineEmits import and call
- expect(content).not.toMatch('defineEmits')
- // should generate correct setup signature
- expect(content).toMatch(
- `setup(__props, { expose: __expose, emit: myEmit }) {`
- )
- // should include context options in default export
- expect(content).toMatch(`export default {
- emits: ['foo', 'bar'],`)
- })
-
test('defineProps/defineEmits in multi-variable declaration', () => {
const { content } = compile(`
- `)
- assertCode(content)
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineOptions')
- // should include context options in default export
- expect(content).toMatch(
- `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `
- )
- })
-
- test('empty argument', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`export default {`)
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineOptions')
- })
-
- it('should emit an error with two defineProps', () => {
- expect(() =>
- compile(`
-
- `)
- ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
- })
-
- it('should emit an error with props or emits property', () => {
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.'
- )
- })
-
- it('should emit an error with type generic', () => {
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot accept type arguments'
- )
- })
-
- it('should emit an error with type assertion', () => {
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
- )
- })
-
- it('should emit an error with declaring props/emits/slots/expose', () => {
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead'
- )
-
- expect(() =>
- compile(`
-
- `)
- ).toThrowError(
- '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead'
- )
- })
- })
-
- test('defineExpose()', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- // should remove defineOptions import and call
- expect(content).not.toMatch('defineExpose')
- // should generate correct setup signature
- expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
- // should replace callee
- expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/)
- })
-
- describe('defineModel()', () => {
- test('basic usage', () => {
- const { content, bindings } = compile(
- `
-
- `,
- { defineModel: true }
- )
- assertCode(content)
- expect(content).toMatch('props: {')
- expect(content).toMatch('"modelValue": { required: true },')
- expect(content).toMatch('"count": {},')
- expect(content).toMatch('emits: ["update:modelValue", "update:count"],')
- expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
- )
- expect(content).toMatch(`const c = _useModel(__props, "count")`)
- expect(content).toMatch(`return { modelValue, c }`)
- expect(content).not.toMatch('defineModel')
-
- expect(bindings).toStrictEqual({
- modelValue: BindingTypes.SETUP_REF,
- count: BindingTypes.PROPS,
- c: BindingTypes.SETUP_REF
- })
- })
-
- test('w/ defineProps and defineEmits', () => {
- const { content, bindings } = compile(
- `
-
- `,
- { defineModel: true }
- )
- assertCode(content)
- expect(content).toMatch(`props: _mergeModels({ foo: String }`)
- expect(content).toMatch(`"modelValue": { default: 0 }`)
- expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
- expect(content).not.toMatch('defineModel')
- expect(bindings).toStrictEqual({
- count: BindingTypes.SETUP_REF,
- foo: BindingTypes.PROPS,
- modelValue: BindingTypes.PROPS
- })
- })
-
- test('w/ array props', () => {
- const { content, bindings } = compile(
- `
-
- `,
- { defineModel: true }
- )
- assertCode(content)
- expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], {
- "count": {},
- })`)
- expect(content).toMatch(`const count = _useModel(__props, "count")`)
- expect(content).not.toMatch('defineModel')
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS,
- count: BindingTypes.SETUP_REF
- })
- })
-
- test('w/ local flag', () => {
- const { content } = compile(
- ``,
- { defineModel: true }
- )
- assertCode(content)
- expect(content).toMatch(
- `_useModel(__props, "modelValue", { local: true })`
- )
- expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`)
- expect(content).toMatch(`_useModel(__props, "baz", { ...x })`)
- expect(content).toMatch(`_useModel(__props, "qux", x)`)
- expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`)
- expect(content).toMatch(`_useModel(__props, "hoist", { local })`)
- })
- })
-
- test('
-
- `)
- assertCode(content)
- })
-
describe('
- `)
- assertCode(content)
- expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
- props: { foo: String },
- emits: ['a', 'b'],
- setup(__props, { expose: __expose, emit }) {`)
- })
-
- test('defineProps w/ type', () => {
- const { content, bindings } = compile(`
- `)
- assertCode(content)
- expect(content).toMatch(`string: { type: String, required: true }`)
- expect(content).toMatch(`number: { type: Number, required: true }`)
- expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
- expect(content).toMatch(`object: { type: Object, required: true }`)
- expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
- expect(content).toMatch(`fn: { type: Function, required: true }`)
- expect(content).toMatch(`functionRef: { type: Function, required: true }`)
- expect(content).toMatch(`objectRef: { type: Object, required: true }`)
- expect(content).toMatch(`dateTime: { type: Date, required: true }`)
- expect(content).toMatch(`array: { type: Array, required: true }`)
- expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
- expect(content).toMatch(`tuple: { type: Array, required: true }`)
- expect(content).toMatch(`set: { type: Set, required: true }`)
- expect(content).toMatch(`literal: { type: String, required: true }`)
- expect(content).toMatch(`optional: { type: null, required: false }`)
- expect(content).toMatch(`recordRef: { type: Object, required: true }`)
- expect(content).toMatch(`interface: { type: Object, required: true }`)
- expect(content).toMatch(`alias: { type: Array, required: true }`)
- expect(content).toMatch(`method: { type: Function, required: true }`)
- expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
- expect(content).toMatch(
- `objectOrFn: { type: [Function, Object], required: true },`
- )
- expect(content).toMatch(`extract: { type: Number, required: true }`)
- expect(content).toMatch(
- `exclude: { type: [Number, Boolean], required: true }`
- )
- expect(content).toMatch(`uppercase: { type: String, required: true }`)
- expect(content).toMatch(`params: { type: Array, required: true }`)
- expect(content).toMatch(`nonNull: { type: String, required: true }`)
- expect(content).toMatch(
- `union: { type: [String, Number], required: true }`
- )
- expect(content).toMatch(`literalUnion: { type: String, required: true }`)
- expect(content).toMatch(
- `literalUnionNumber: { type: Number, required: true }`
- )
- expect(content).toMatch(
- `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
- )
- expect(content).toMatch(`intersection: { type: Object, required: true }`)
- expect(content).toMatch(`intersection2: { type: String, required: true }`)
- expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
- expect(content).toMatch(`unknown: { type: null, required: true }`)
- // uninon containing unknown type: skip check
- expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
- // intersection containing unknown type: narrow to the known types
- expect(content).toMatch(
- `unknownIntersection: { type: Object, required: true },`
- )
- expect(content).toMatch(
- `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`
- )
- expect(content).toMatch(
- `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`
- )
- expect(bindings).toStrictEqual({
- string: BindingTypes.PROPS,
- number: BindingTypes.PROPS,
- boolean: BindingTypes.PROPS,
- object: BindingTypes.PROPS,
- objectLiteral: BindingTypes.PROPS,
- fn: BindingTypes.PROPS,
- functionRef: BindingTypes.PROPS,
- objectRef: BindingTypes.PROPS,
- dateTime: BindingTypes.PROPS,
- array: BindingTypes.PROPS,
- arrayRef: BindingTypes.PROPS,
- tuple: BindingTypes.PROPS,
- set: BindingTypes.PROPS,
- literal: BindingTypes.PROPS,
- optional: BindingTypes.PROPS,
- recordRef: BindingTypes.PROPS,
- interface: BindingTypes.PROPS,
- alias: BindingTypes.PROPS,
- method: BindingTypes.PROPS,
- symbol: BindingTypes.PROPS,
- objectOrFn: BindingTypes.PROPS,
- extract: BindingTypes.PROPS,
- exclude: BindingTypes.PROPS,
- union: BindingTypes.PROPS,
- literalUnion: BindingTypes.PROPS,
- literalUnionNumber: BindingTypes.PROPS,
- literalUnionMixed: BindingTypes.PROPS,
- intersection: BindingTypes.PROPS,
- intersection2: BindingTypes.PROPS,
- foo: BindingTypes.PROPS,
- uppercase: BindingTypes.PROPS,
- params: BindingTypes.PROPS,
- nonNull: BindingTypes.PROPS,
- unknown: BindingTypes.PROPS,
- unknownUnion: BindingTypes.PROPS,
- unknownIntersection: BindingTypes.PROPS,
- unknownUnionWithBoolean: BindingTypes.PROPS,
- unknownUnionWithFunction: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ interface', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ extends interface', () => {
- const { content, bindings } = compile(`
-
-
- `)
- assertCode(content)
- expect(content).toMatch(`z: { type: Number, required: true }`)
- expect(content).toMatch(`y: { type: String, required: true }`)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS,
- y: BindingTypes.PROPS,
- z: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ exported interface', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ exported interface in normal script', () => {
- const { content, bindings } = compile(`
-
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ type alias', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ exported type alias', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`x: { type: Number, required: false }`)
- expect(bindings).toStrictEqual({
- x: BindingTypes.PROPS
- })
- })
-
- test('defineProps w/ TS assertion', () => {
- const { content, bindings } = compile(`
-
- `)
- expect(content).toMatch(`props: ['foo']`)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS
- })
- })
-
- test('withDefaults (static)', () => {
- const { content, bindings } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(
- `foo: { type: String, required: false, default: 'hi' }`
- )
- expect(content).toMatch(`bar: { type: Number, required: false }`)
- expect(content).toMatch(`baz: { type: Boolean, required: true }`)
- expect(content).toMatch(
- `qux: { type: Function, required: false, default() { return 1 } }`
- )
- expect(content).toMatch(
- `quux: { type: Function, required: false, default() { } }`
- )
- expect(content).toMatch(
- `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`
- )
- expect(content).toMatch(
- `fred: { type: String, required: false, get default() { return 'fred' } }`
- )
- expect(content).toMatch(`const props = __props`)
- expect(bindings).toStrictEqual({
- foo: BindingTypes.PROPS,
- bar: BindingTypes.PROPS,
- baz: BindingTypes.PROPS,
- qux: BindingTypes.PROPS,
- quux: BindingTypes.PROPS,
- quuxx: BindingTypes.PROPS,
- fred: BindingTypes.PROPS,
- props: BindingTypes.SETUP_CONST
- })
- })
-
- test('withDefaults (static) + normal script', () => {
- const { content } = compile(`
-
-
- `)
- assertCode(content)
- })
-
- // #7111
- test('withDefaults (static) w/ production mode', () => {
- const { content } = compile(
- `
-
- `,
- { isProd: true }
- )
- assertCode(content)
- expect(content).toMatch(`const props = __props`)
-
- // foo has no default value, the Function can be dropped
- expect(content).toMatch(`foo: {}`)
- expect(content).toMatch(`bar: { type: Boolean }`)
- expect(content).toMatch(
- `baz: { type: [Boolean, Function], default: true }`
- )
- expect(content).toMatch(`qux: { default: 'hi' }`)
- })
-
- test('withDefaults (dynamic)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
- expect(content).toMatch(
- `
- _mergeDefaults({
- foo: { type: String, required: false },
- bar: { type: Number, required: false },
- baz: { type: Boolean, required: true }
- }, { ...defaults })`.trim()
- )
- })
-
- test('withDefaults (reference)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
- expect(content).toMatch(
- `
- _mergeDefaults({
- foo: { type: String, required: false },
- bar: { type: Number, required: false },
- baz: { type: Boolean, required: true }
- }, defaults)`.trim()
- )
- })
-
- // #7111
- test('withDefaults (dynamic) w/ production mode', () => {
- const { content } = compile(
- `
-
- `,
- { isProd: true }
- )
- assertCode(content)
- expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
- expect(content).toMatch(
- `
- _mergeDefaults({
- foo: { type: Function },
- bar: { type: Boolean },
- baz: { type: [Boolean, Function] },
- qux: {}
- }, { ...defaults })`.trim()
- )
- })
-
- test('withDefaults w/ dynamic object method', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
- expect(content).toMatch(
- `
- _mergeDefaults({
- foo: { type: Function, required: false }
- }, {
- ['fo' + 'o']() { return 'foo' }
- })`.trim()
- )
- })
-
- test('defineEmits w/ type', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (union)', () => {
- const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
- expect(() =>
- compile(`
-
- `)
- ).toThrow()
- })
-
- test('defineEmits w/ type (type literal w/ call signatures)', () => {
- const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
- })
-
- test('defineEmits w/ type (interface)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (exported interface)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type from normal script', () => {
- const { content } = compile(`
-
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (type alias)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (exported type alias)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (referenced function type)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- test('defineEmits w/ type (referenced exported function type)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- })
-
- // #5393
- test('defineEmits w/ type (interface ts type)', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`emits: ['foo']`)
- })
-
- test('defineEmits w/ type (property syntax)', () => {
- const { content } = compile(`
-
- `)
- expect(content).toMatch(`emits: ["foo", "bar"]`)
- assertCode(content)
- })
-
- // #8040
- test('defineEmits w/ type (property syntax string literal)', () => {
- const { content } = compile(`
-
- `)
- expect(content).toMatch(`emits: ["foo:bar"]`)
- assertCode(content)
- })
-
- describe('defineSlots()', () => {
- test('basic usage', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`const slots = _useSlots()`)
- expect(content).not.toMatch('defineSlots')
- })
-
- test('w/o return value', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).not.toMatch('defineSlots')
- expect(content).not.toMatch(`_useSlots`)
- })
-
- test('w/o generic params', () => {
- const { content } = compile(`
-
- `)
- assertCode(content)
- expect(content).toMatch(`const slots = _useSlots()`)
- expect(content).not.toMatch('defineSlots')
- })
- })
-
- describe('defineModel()', () => {
- test('basic usage', () => {
- const { content, bindings } = compile(
- `
-
- `,
- { defineModel: true }
- )
- assertCode(content)
- expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
- expect(content).toMatch('"count": { type: Number }')
- expect(content).toMatch(
- '"disabled": { type: Number, ...{ required: false } }'
- )
- expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
- expect(content).toMatch(
- 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]'
- )
-
- expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
- )
- expect(content).toMatch(`const count = _useModel(__props, "count")`)
- expect(content).toMatch(
- `const disabled = _useModel(__props, "disabled")`
- )
- expect(content).toMatch(`const any = _useModel(__props, "any")`)
-
- expect(bindings).toStrictEqual({
- modelValue: BindingTypes.SETUP_REF,
- count: BindingTypes.SETUP_REF,
- disabled: BindingTypes.SETUP_REF,
- any: BindingTypes.SETUP_REF
- })
- })
-
- test('w/ production mode', () => {
- const { content, bindings } = compile(
- `
-
- `,
- { defineModel: true, isProd: true }
- )
- assertCode(content)
- expect(content).toMatch('"modelValue": { type: Boolean }')
- expect(content).toMatch('"fn": {}')
- expect(content).toMatch(
- '"fnWithDefault": { type: Function, ...{ default: () => null } },'
- )
- expect(content).toMatch('"str": {}')
- expect(content).toMatch('"optional": { required: false }')
- expect(content).toMatch(
- 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
- )
- expect(content).toMatch(
- `const modelValue = _useModel(__props, "modelValue")`
- )
- expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
- expect(content).toMatch(`const str = _useModel(__props, "str")`)
- expect(bindings).toStrictEqual({
- modelValue: BindingTypes.SETUP_REF,
- fn: BindingTypes.SETUP_REF,
- fnWithDefault: BindingTypes.SETUP_REF,
- str: BindingTypes.SETUP_REF,
- optional: BindingTypes.SETUP_REF
- })
- })
- })
-
test('runtime Enum', () => {
const { content, bindings } = compile(
``,
- { hoistStatic: true }
- ).content
- ).toMatch(`foo: { type: Number`)
-
- expect(
- compile(
- ``,
- { hoistStatic: true }
- ).content
- ).toMatch(`foo: { type: String`)
-
- expect(
- compile(
- ``,
- { hoistStatic: true }
- ).content
- ).toMatch(`foo: { type: [String, Number]`)
-
- expect(
- compile(
- ``,
- { hoistStatic: true }
- ).content
- ).toMatch(`foo: { type: Number`)
- })
-
test('import type', () => {
const { content } = compile(
``)
- }).toThrow(`cannot accept both type and non-type arguments`)
-
- expect(() => {
- compile(``)
- }).toThrow(`cannot accept both type and non-type arguments`)
- })
-
test('defineProps/Emit() referencing local var', () => {
expect(() =>
compile(``).content
)
})
-
- test('mixed usage of property / call signature in defineEmits', () => {
- expect(() =>
- compile(``)
- ).toThrow(
- `defineEmits() type cannot mixed call signature and property syntax.`
- )
- })
})
})
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap
new file mode 100644
index 00000000000..5add78a28b3
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineEmits.spec.ts.snap
@@ -0,0 +1,232 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`defineEmits > basic usage 1`] = `
+"export default {
+ emits: ['foo', 'bar'],
+ setup(__props, { expose: __expose, emit: myEmit }) {
+ __expose();
+
+
+
+return { myEmit }
+}
+
+}"
+`;
+
+exports[`defineEmits > w/ runtime options 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ emits: ['a', 'b'],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (exported interface) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (exported type alias) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export type Emits = { (e: 'foo' | 'bar'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (interface ts type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Emits { (e: 'foo'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: ['foo'],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (interface) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo:bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (property syntax) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (referenced exported function type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+export type Emits = (e: 'foo' | 'bar') => void
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (referenced function type) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type Emits = (e: 'foo' | 'bar') => void
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (type alias) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+type Emits = { (e: 'foo' | 'bar'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\", \\"baz\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
+
+exports[`defineEmits > w/ type from normal script 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+ export interface Emits { (e: 'foo' | 'bar'): void }
+
+export default /*#__PURE__*/_defineComponent({
+ emits: [\\"foo\\", \\"bar\\"],
+ setup(__props, { expose: __expose, emit }) {
+ __expose();
+
+
+
+return { emit }
+}
+
+})"
+`;
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap
new file mode 100644
index 00000000000..d72726460bf
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineExpose.spec.ts.snap
@@ -0,0 +1,28 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`
+ `)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ myEmit: BindingTypes.SETUP_CONST
+ })
+ // should remove defineEmits import and call
+ expect(content).not.toMatch('defineEmits')
+ // should generate correct setup signature
+ expect(content).toMatch(
+ `setup(__props, { expose: __expose, emit: myEmit }) {`
+ )
+ // should include context options in default export
+ expect(content).toMatch(`export default {
+ emits: ['foo', 'bar'],`)
+ })
+
+ test('w/ runtime options', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
+ emits: ['a', 'b'],
+ setup(__props, { expose: __expose, emit }) {`)
+ })
+
+ test('w/ type', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (union)', () => {
+ const type = `((e: 'foo' | 'bar') => void) | ((e: 'baz', id: number) => void)`
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrow()
+ })
+
+ test('w/ type (type literal w/ call signatures)', () => {
+ const type = `{(e: 'foo' | 'bar'): void; (e: 'baz', id: number): void;}`
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar", "baz"]`)
+ })
+
+ test('w/ type (interface)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (exported interface)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type from normal script', () => {
+ const { content } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (type alias)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (exported type alias)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (referenced function type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ test('w/ type (referenced exported function type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ })
+
+ // #5393
+ test('w/ type (interface ts type)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`emits: ['foo']`)
+ })
+
+ test('w/ type (property syntax)', () => {
+ const { content } = compile(`
+
+ `)
+ expect(content).toMatch(`emits: ["foo", "bar"]`)
+ assertCode(content)
+ })
+
+ // #8040
+ test('w/ type (property syntax string literal)', () => {
+ const { content } = compile(`
+
+ `)
+ expect(content).toMatch(`emits: ["foo:bar"]`)
+ assertCode(content)
+ })
+
+ describe('errors', () => {
+ test('w/ both type and non-type args', () => {
+ expect(() => {
+ compile(``)
+ }).toThrow(`cannot accept both type and non-type arguments`)
+ })
+
+ test('mixed usage of property / call signature', () => {
+ expect(() =>
+ compile(``)
+ ).toThrow(
+ `defineEmits() type cannot mixed call signature and property syntax.`
+ )
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts
new file mode 100644
index 00000000000..8ddd28a89e6
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineExpose.spec.ts
@@ -0,0 +1,26 @@
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+test('defineExpose()', () => {
+ const { content } = compile(`
+
+`)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineExpose')
+ // should generate correct setup signature
+ expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
+ // should replace callee
+ expect(content).toMatch(/\b__expose\(\{ foo: 123 \}\)/)
+})
+
+test('
+
+ `)
+ assertCode(content)
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
new file mode 100644
index 00000000000..61a9adcbe0d
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
@@ -0,0 +1,179 @@
+import { BindingTypes } from '@vue/compiler-core'
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+describe('defineModel()', () => {
+ test('basic usage', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { defineModel: true }
+ )
+ assertCode(content)
+ expect(content).toMatch('props: {')
+ expect(content).toMatch('"modelValue": { required: true },')
+ expect(content).toMatch('"count": {},')
+ expect(content).toMatch('emits: ["update:modelValue", "update:count"],')
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`
+ )
+ expect(content).toMatch(`const c = _useModel(__props, "count")`)
+ expect(content).toMatch(`return { modelValue, c }`)
+ expect(content).not.toMatch('defineModel')
+
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ count: BindingTypes.PROPS,
+ c: BindingTypes.SETUP_REF
+ })
+ })
+
+ test('w/ defineProps and defineEmits', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { defineModel: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`props: _mergeModels({ foo: String }`)
+ expect(content).toMatch(`"modelValue": { default: 0 }`)
+ expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
+ expect(content).not.toMatch('defineModel')
+ expect(bindings).toStrictEqual({
+ count: BindingTypes.SETUP_REF,
+ foo: BindingTypes.PROPS,
+ modelValue: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ array props', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { defineModel: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`props: _mergeModels(['foo', 'bar'], {
+ "count": {},
+ })`)
+ expect(content).toMatch(`const count = _useModel(__props, "count")`)
+ expect(content).not.toMatch('defineModel')
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ count: BindingTypes.SETUP_REF
+ })
+ })
+
+ test('w/ local flag', () => {
+ const { content } = compile(
+ ``,
+ { defineModel: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`)
+ expect(content).toMatch(`_useModel(__props, "bar", { [key]: true })`)
+ expect(content).toMatch(`_useModel(__props, "baz", { ...x })`)
+ expect(content).toMatch(`_useModel(__props, "qux", x)`)
+ expect(content).toMatch(`_useModel(__props, "foo2", { local: true })`)
+ expect(content).toMatch(`_useModel(__props, "hoist", { local })`)
+ })
+
+ test('w/ types, basic usage', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { defineModel: true }
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
+ expect(content).toMatch('"count": { type: Number }')
+ expect(content).toMatch(
+ '"disabled": { type: Number, ...{ required: false } }'
+ )
+ expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
+ expect(content).toMatch(
+ 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]'
+ )
+
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`
+ )
+ expect(content).toMatch(`const count = _useModel(__props, "count")`)
+ expect(content).toMatch(`const disabled = _useModel(__props, "disabled")`)
+ expect(content).toMatch(`const any = _useModel(__props, "any")`)
+
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ count: BindingTypes.SETUP_REF,
+ disabled: BindingTypes.SETUP_REF,
+ any: BindingTypes.SETUP_REF
+ })
+ })
+
+ test('w/ types, production mode', () => {
+ const { content, bindings } = compile(
+ `
+
+ `,
+ { defineModel: true, isProd: true }
+ )
+ assertCode(content)
+ expect(content).toMatch('"modelValue": { type: Boolean }')
+ expect(content).toMatch('"fn": {}')
+ expect(content).toMatch(
+ '"fnWithDefault": { type: Function, ...{ default: () => null } },'
+ )
+ expect(content).toMatch('"str": {}')
+ expect(content).toMatch('"optional": { required: false }')
+ expect(content).toMatch(
+ 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
+ )
+ expect(content).toMatch(
+ `const modelValue = _useModel(__props, "modelValue")`
+ )
+ expect(content).toMatch(`const fn = _useModel(__props, "fn")`)
+ expect(content).toMatch(`const str = _useModel(__props, "str")`)
+ expect(bindings).toStrictEqual({
+ modelValue: BindingTypes.SETUP_REF,
+ fn: BindingTypes.SETUP_REF,
+ fnWithDefault: BindingTypes.SETUP_REF,
+ str: BindingTypes.SETUP_REF,
+ optional: BindingTypes.SETUP_REF
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
new file mode 100644
index 00000000000..5337a53917c
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineOptions.spec.ts
@@ -0,0 +1,149 @@
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+describe('defineOptions()', () => {
+ test('basic usage', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
+ // should include context options in default export
+ expect(content).toMatch(
+ `export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `
+ )
+ })
+
+ test('empty argument', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default {`)
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineOptions')
+ })
+
+ it('should emit an error with two defineProps', () => {
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError('[@vue/compiler-sfc] duplicate defineOptions() call')
+ })
+
+ it('should emit an error with props or emits property', () => {
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead.'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead.'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead.'
+ )
+ })
+
+ it('should emit an error with type generic', () => {
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot accept type arguments'
+ )
+ })
+
+ it('should emit an error with type assertion', () => {
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead.'
+ )
+ })
+
+ it('should emit an error with declaring props/emits/slots/expose', () => {
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare props. Use defineProps() instead'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare emits. Use defineEmits() instead'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare expose. Use defineExpose() instead'
+ )
+
+ expect(() =>
+ compile(`
+
+ `)
+ ).toThrowError(
+ '[@vue/compiler-sfc] defineOptions() cannot be used to declare slots. Use defineSlots() instead'
+ )
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
new file mode 100644
index 00000000000..cf61c98406c
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineProps.spec.ts
@@ -0,0 +1,583 @@
+import { BindingTypes } from '@vue/compiler-core'
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+describe('defineProps', () => {
+ test('basic usage', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ // should generate working code
+ assertCode(content)
+ // should analyze bindings
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.LITERAL_CONST,
+ props: BindingTypes.SETUP_REACTIVE_CONST
+ })
+
+ // should remove defineOptions import and call
+ expect(content).not.toMatch('defineProps')
+ // should generate correct setup signature
+ expect(content).toMatch(`setup(__props, { expose: __expose }) {`)
+ // should assign user identifier to it
+ expect(content).toMatch(`const props = __props`)
+ // should include context options in default export
+ expect(content).toMatch(`export default {
+ props: {
+ foo: String
+},`)
+ })
+
+ test('w/ external definition', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default {
+ props: propsModel,`)
+ })
+
+ // #4764
+ test('w/ leading code', () => {
+ const { content } = compile(`
+
+ `)
+ // props declaration should be inside setup, not moved along with the import
+ expect(content).not.toMatch(`const props = __props\nimport`)
+ assertCode(content)
+ })
+
+ test('defineProps w/ runtime options', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
+ props: { foo: String },
+ setup(__props, { expose: __expose }) {`)
+ })
+
+ test('w/ type', () => {
+ const { content, bindings } = compile(`
+ `)
+ assertCode(content)
+ expect(content).toMatch(`string: { type: String, required: true }`)
+ expect(content).toMatch(`number: { type: Number, required: true }`)
+ expect(content).toMatch(`boolean: { type: Boolean, required: true }`)
+ expect(content).toMatch(`object: { type: Object, required: true }`)
+ expect(content).toMatch(`objectLiteral: { type: Object, required: true }`)
+ expect(content).toMatch(`fn: { type: Function, required: true }`)
+ expect(content).toMatch(`functionRef: { type: Function, required: true }`)
+ expect(content).toMatch(`objectRef: { type: Object, required: true }`)
+ expect(content).toMatch(`dateTime: { type: Date, required: true }`)
+ expect(content).toMatch(`array: { type: Array, required: true }`)
+ expect(content).toMatch(`arrayRef: { type: Array, required: true }`)
+ expect(content).toMatch(`tuple: { type: Array, required: true }`)
+ expect(content).toMatch(`set: { type: Set, required: true }`)
+ expect(content).toMatch(`literal: { type: String, required: true }`)
+ expect(content).toMatch(`optional: { type: null, required: false }`)
+ expect(content).toMatch(`recordRef: { type: Object, required: true }`)
+ expect(content).toMatch(`interface: { type: Object, required: true }`)
+ expect(content).toMatch(`alias: { type: Array, required: true }`)
+ expect(content).toMatch(`method: { type: Function, required: true }`)
+ expect(content).toMatch(`symbol: { type: Symbol, required: true }`)
+ expect(content).toMatch(
+ `objectOrFn: { type: [Function, Object], required: true },`
+ )
+ expect(content).toMatch(`extract: { type: Number, required: true }`)
+ expect(content).toMatch(
+ `exclude: { type: [Number, Boolean], required: true }`
+ )
+ expect(content).toMatch(`uppercase: { type: String, required: true }`)
+ expect(content).toMatch(`params: { type: Array, required: true }`)
+ expect(content).toMatch(`nonNull: { type: String, required: true }`)
+ expect(content).toMatch(`union: { type: [String, Number], required: true }`)
+ expect(content).toMatch(`literalUnion: { type: String, required: true }`)
+ expect(content).toMatch(
+ `literalUnionNumber: { type: Number, required: true }`
+ )
+ expect(content).toMatch(
+ `literalUnionMixed: { type: [String, Number, Boolean], required: true }`
+ )
+ expect(content).toMatch(`intersection: { type: Object, required: true }`)
+ expect(content).toMatch(`intersection2: { type: String, required: true }`)
+ expect(content).toMatch(`foo: { type: [Function, null], required: true }`)
+ expect(content).toMatch(`unknown: { type: null, required: true }`)
+ // uninon containing unknown type: skip check
+ expect(content).toMatch(`unknownUnion: { type: null, required: true }`)
+ // intersection containing unknown type: narrow to the known types
+ expect(content).toMatch(
+ `unknownIntersection: { type: Object, required: true },`
+ )
+ expect(content).toMatch(
+ `unknownUnionWithBoolean: { type: Boolean, required: true, skipCheck: true },`
+ )
+ expect(content).toMatch(
+ `unknownUnionWithFunction: { type: Function, required: true, skipCheck: true }`
+ )
+ expect(bindings).toStrictEqual({
+ string: BindingTypes.PROPS,
+ number: BindingTypes.PROPS,
+ boolean: BindingTypes.PROPS,
+ object: BindingTypes.PROPS,
+ objectLiteral: BindingTypes.PROPS,
+ fn: BindingTypes.PROPS,
+ functionRef: BindingTypes.PROPS,
+ objectRef: BindingTypes.PROPS,
+ dateTime: BindingTypes.PROPS,
+ array: BindingTypes.PROPS,
+ arrayRef: BindingTypes.PROPS,
+ tuple: BindingTypes.PROPS,
+ set: BindingTypes.PROPS,
+ literal: BindingTypes.PROPS,
+ optional: BindingTypes.PROPS,
+ recordRef: BindingTypes.PROPS,
+ interface: BindingTypes.PROPS,
+ alias: BindingTypes.PROPS,
+ method: BindingTypes.PROPS,
+ symbol: BindingTypes.PROPS,
+ objectOrFn: BindingTypes.PROPS,
+ extract: BindingTypes.PROPS,
+ exclude: BindingTypes.PROPS,
+ union: BindingTypes.PROPS,
+ literalUnion: BindingTypes.PROPS,
+ literalUnionNumber: BindingTypes.PROPS,
+ literalUnionMixed: BindingTypes.PROPS,
+ intersection: BindingTypes.PROPS,
+ intersection2: BindingTypes.PROPS,
+ foo: BindingTypes.PROPS,
+ uppercase: BindingTypes.PROPS,
+ params: BindingTypes.PROPS,
+ nonNull: BindingTypes.PROPS,
+ unknown: BindingTypes.PROPS,
+ unknownUnion: BindingTypes.PROPS,
+ unknownIntersection: BindingTypes.PROPS,
+ unknownUnionWithBoolean: BindingTypes.PROPS,
+ unknownUnionWithFunction: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ interface', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ extends interface', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`z: { type: Number, required: true }`)
+ expect(content).toMatch(`y: { type: String, required: true }`)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS,
+ y: BindingTypes.PROPS,
+ z: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ exported interface', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ exported interface in normal script', () => {
+ const { content, bindings } = compile(`
+
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ type alias', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ exported type alias', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`x: { type: Number, required: false }`)
+ expect(bindings).toStrictEqual({
+ x: BindingTypes.PROPS
+ })
+ })
+
+ test('w/ TS assertion', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ expect(content).toMatch(`props: ['foo']`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS
+ })
+ })
+
+ test('withDefaults (static)', () => {
+ const { content, bindings } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(
+ `foo: { type: String, required: false, default: 'hi' }`
+ )
+ expect(content).toMatch(`bar: { type: Number, required: false }`)
+ expect(content).toMatch(`baz: { type: Boolean, required: true }`)
+ expect(content).toMatch(
+ `qux: { type: Function, required: false, default() { return 1 } }`
+ )
+ expect(content).toMatch(
+ `quux: { type: Function, required: false, default() { } }`
+ )
+ expect(content).toMatch(
+ `quuxx: { type: Promise, required: false, async default() { return await Promise.resolve('hi') } }`
+ )
+ expect(content).toMatch(
+ `fred: { type: String, required: false, get default() { return 'fred' } }`
+ )
+ expect(content).toMatch(`const props = __props`)
+ expect(bindings).toStrictEqual({
+ foo: BindingTypes.PROPS,
+ bar: BindingTypes.PROPS,
+ baz: BindingTypes.PROPS,
+ qux: BindingTypes.PROPS,
+ quux: BindingTypes.PROPS,
+ quuxx: BindingTypes.PROPS,
+ fred: BindingTypes.PROPS,
+ props: BindingTypes.SETUP_CONST
+ })
+ })
+
+ test('withDefaults (static) + normal script', () => {
+ const { content } = compile(`
+
+
+ `)
+ assertCode(content)
+ })
+
+ // #7111
+ test('withDefaults (static) w/ production mode', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`const props = __props`)
+
+ // foo has no default value, the Function can be dropped
+ expect(content).toMatch(`foo: {}`)
+ expect(content).toMatch(`bar: { type: Boolean }`)
+ expect(content).toMatch(`baz: { type: [Boolean, Function], default: true }`)
+ expect(content).toMatch(`qux: { default: 'hi' }`)
+ })
+
+ test('withDefaults (dynamic)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: String, required: false },
+ bar: { type: Number, required: false },
+ baz: { type: Boolean, required: true }
+ }, { ...defaults })`.trim()
+ )
+ })
+
+ test('withDefaults (reference)', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: String, required: false },
+ bar: { type: Number, required: false },
+ baz: { type: Boolean, required: true }
+ }, defaults)`.trim()
+ )
+ })
+
+ // #7111
+ test('withDefaults (dynamic) w/ production mode', () => {
+ const { content } = compile(
+ `
+
+ `,
+ { isProd: true }
+ )
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: Function },
+ bar: { type: Boolean },
+ baz: { type: [Boolean, Function] },
+ qux: {}
+ }, { ...defaults })`.trim()
+ )
+ })
+
+ test('withDefaults w/ dynamic object method', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`import { mergeDefaults as _mergeDefaults`)
+ expect(content).toMatch(
+ `
+ _mergeDefaults({
+ foo: { type: Function, required: false }
+ }, {
+ ['fo' + 'o']() { return 'foo' }
+ })`.trim()
+ )
+ })
+
+ test('runtime inference for Enum', () => {
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true }
+ ).content
+ ).toMatch(`foo: { type: Number`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true }
+ ).content
+ ).toMatch(`foo: { type: String`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true }
+ ).content
+ ).toMatch(`foo: { type: [String, Number]`)
+
+ expect(
+ compile(
+ ``,
+ { hoistStatic: true }
+ ).content
+ ).toMatch(`foo: { type: Number`)
+ })
+
+ describe('errors', () => {
+ test('w/ both type and non-type args', () => {
+ expect(() => {
+ compile(``)
+ }).toThrow(`cannot accept both type and non-type arguments`)
+ })
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
similarity index 99%
rename from packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts
rename to packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
index e00d7d48b97..a459d80ff29 100644
--- a/packages/compiler-sfc/__tests__/compileScriptPropsDestructure.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/definePropsDestructure.spec.ts
@@ -1,6 +1,6 @@
import { BindingTypes } from '@vue/compiler-core'
-import { SFCScriptCompileOptions } from '../src'
-import { compileSFCScript, assertCode } from './utils'
+import { SFCScriptCompileOptions } from '../../src'
+import { compileSFCScript, assertCode } from '../utils'
describe('sfc props transform', () => {
function compile(src: string, options?: Partial) {
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts
new file mode 100644
index 00000000000..c7becacc02a
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/compileScript/defineSlots.spec.ts
@@ -0,0 +1,40 @@
+import { compileSFCScript as compile, assertCode } from '../utils'
+
+describe('defineSlots()', () => {
+ test('basic usage', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const slots = _useSlots()`)
+ expect(content).not.toMatch('defineSlots')
+ })
+
+ test('w/o return value', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).not.toMatch('defineSlots')
+ expect(content).not.toMatch(`_useSlots`)
+ })
+
+ test('w/o generic params', () => {
+ const { content } = compile(`
+
+ `)
+ assertCode(content)
+ expect(content).toMatch(`const slots = _useSlots()`)
+ expect(content).not.toMatch('defineSlots')
+ })
+})
diff --git a/packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts
similarity index 97%
rename from packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts
rename to packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts
index 4879dd5f924..614a5e75bce 100644
--- a/packages/compiler-sfc/__tests__/compileScriptHoistStatic.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/hoistStatic.spec.ts
@@ -1,6 +1,6 @@
import { BindingTypes } from '@vue/compiler-core'
-import { SFCScriptCompileOptions } from '../src'
-import { compileSFCScript, assertCode } from './utils'
+import { SFCScriptCompileOptions } from '../../src'
+import { compileSFCScript, assertCode } from '../utils'
describe('sfc hoist static', () => {
function compile(src: string, options?: Partial) {
diff --git a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts b/packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts
similarity index 98%
rename from packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts
rename to packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts
index 8ae5275661e..44d51c14e75 100644
--- a/packages/compiler-sfc/__tests__/compileScriptRefTransform.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/reactivityTransform.spec.ts
@@ -1,5 +1,6 @@
+// TODO remove in 3.4
import { BindingTypes } from '@vue/compiler-core'
-import { compileSFCScript as compile, assertCode } from './utils'
+import { compileSFCScript as compile, assertCode } from '../utils'
// this file only tests integration with SFC - main test case for the ref
// transform can be found in /packages/reactivity-transform/__tests__