diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index 23a3741afb2..f6136eb1bac 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -653,6 +653,100 @@ return { }
}"
`;
+exports[`SFC 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()', () => {
@@ -323,6 +365,109 @@ defineExpose({ foo: 123 })
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('
+ `,
+ { 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(
`