Skip to content

Commit

Permalink
feat: addRequiredToModels
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Apr 5, 2023
1 parent 11149ec commit e56ffa8
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 95 deletions.
Expand Up @@ -654,13 +654,13 @@ return { }
`;

exports[`SFC compile <script setup> > defineModel() > basic usage 1`] = `
"import { useModel as _useModel } from 'vue'
"import { useModel as _useModel, addRequiredToModels as _addRequiredToModels } from 'vue'

export default {
props: {
\\"modelValue\\": Object.assign({ required: true }, { required: true }),
\\"count\\": { required: true },
},
props: _addRequiredToModels({
\\"modelValue\\": { required: true },
\\"count\\": {},
}),
emits: [\\"update:modelValue\\", \\"update:count\\"],
setup(__props, { expose: __expose }) {
__expose();
Expand All @@ -675,12 +675,12 @@ return { modelValue, c }
`;

exports[`SFC compile <script setup> > defineModel() > w/ array props 1`] = `
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
"import { useModel as _useModel, addRequiredToModels as _addRequiredToModels, mergeModels as _mergeModels } from 'vue'

export default {
props: _mergeModels(['foo', 'bar'], {
\\"count\\": { required: true },
}),
props: _mergeModels(['foo', 'bar'], _addRequiredToModels({
\\"count\\": {},
})),
emits: [\\"update:count\\"],
setup(__props, { expose: __expose }) {
__expose();
Expand All @@ -695,12 +695,12 @@ return { count }
`;

exports[`SFC compile <script setup> > defineModel() > w/ defineProps and defineEmits 1`] = `
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
"import { useModel as _useModel, addRequiredToModels as _addRequiredToModels, mergeModels as _mergeModels } from 'vue'

export default {
props: _mergeModels({ foo: String }, {
\\"modelValue\\": Object.assign({ required: true }, { default: 0 }),
}),
props: _mergeModels({ foo: String }, _addRequiredToModels({
\\"modelValue\\": { default: 0 },
})),
emits: _mergeModels(['change'], [\\"update:modelValue\\"]),
setup(__props, { expose: __expose }) {
__expose();
Expand Down Expand Up @@ -1643,14 +1643,14 @@ return { emit }
`;

exports[`SFC compile <script setup> > with TypeScript > defineModel() > basic usage 1`] = `
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
"import { useModel as _useModel, addRequiredToModels as _addRequiredToModels, defineComponent as _defineComponent } from 'vue'

export default /*#__PURE__*/_defineComponent({
props: {
\\"modelValue\\": { type: [Boolean, String], required: true },
\\"count\\": { type: Number, required: true },
\\"disabled\\": { type: Number, required: true, ...{ required: false } },
},
props: _addRequiredToModels({
\\"modelValue\\": { type: [Boolean, String] },
\\"count\\": { type: Number },
\\"disabled\\": { type: Number, ...{ required: false } },
}),
emits: [\\"update:modelValue\\", \\"update:count\\", \\"update:disabled\\"],
setup(__props, { expose: __expose }) {
__expose();
Expand All @@ -1666,16 +1666,16 @@ return { modelValue, count, disabled }
`;

exports[`SFC compile <script setup> > with TypeScript > defineModel() > w/ production mode 1`] = `
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
"import { useModel as _useModel, addRequiredToModels as _addRequiredToModels, defineComponent as _defineComponent } from 'vue'

export default /*#__PURE__*/_defineComponent({
props: {
props: _addRequiredToModels({
\\"modelValue\\": Boolean,
\\"fn\\": { },
\\"fn\\": {},
\\"fnWithDefault\\": { type: Function, ...{ default: () => null } },
\\"str\\": { },
\\"str\\": {},
\\"optional\\": { required: false },
},
}),
emits: [\\"update:modelValue\\", \\"update:fn\\", \\"update:fnWithDefault\\", \\"update:str\\", \\"update:optional\\"],
setup(__props, { expose: __expose }) {
__expose();
Expand Down
29 changes: 13 additions & 16 deletions packages/compiler-sfc/__tests__/compileScript.spec.ts
Expand Up @@ -377,10 +377,9 @@ defineExpose({ foo: 123 })
{ defineModel: true }
)
assertCode(content)
expect(content).toMatch(
'"modelValue": Object.assign({ required: true }, { required: true }),'
)
expect(content).toMatch('"count": { required: true },')
expect(content).toMatch('props: _addRequiredToModels({')
expect(content).toMatch('"modelValue": { required: true },')
expect(content).toMatch('"count": {},')
expect(content).toMatch('emits: ["update:modelValue", "update:count"],')
expect(content).toMatch(`const modelValue = _useModel("modelValue")`)
expect(content).toMatch(`const c = _useModel("count")`)
Expand All @@ -407,9 +406,7 @@ defineExpose({ foo: 123 })
)
assertCode(content)
expect(content).toMatch(`props: _mergeModels({ foo: String }`)
expect(content).toMatch(
`"modelValue": Object.assign({ required: true }, { default: 0 })`
)
expect(content).toMatch(`"modelValue": { default: 0 }`)
expect(content).toMatch(`const count = _useModel("modelValue")`)
expect(content).not.toMatch('defineModel')
expect(bindings).toStrictEqual({
Expand All @@ -430,8 +427,10 @@ defineExpose({ foo: 123 })
{ defineModel: true }
)
assertCode(content)
expect(content).toMatch(`_mergeModels(['foo', 'bar'], {`)
expect(content).toMatch(`"count": { required: true }`)
expect(content)
.toMatch(`props: _mergeModels(['foo', 'bar'], _addRequiredToModels({
"count": {},
}))`)
expect(content).toMatch(`const count = _useModel("count")`)
expect(content).not.toMatch('defineModel')
expect(bindings).toStrictEqual({
Expand Down Expand Up @@ -1800,12 +1799,10 @@ const emit = defineEmits(['a', 'b'])
{ defineModel: true }
)
assertCode(content)
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
expect(content).toMatch('"count": { type: Number }')
expect(content).toMatch(
'"modelValue": { type: [Boolean, String], required: true }'
)
expect(content).toMatch('"count": { type: Number, required: true }')
expect(content).toMatch(
'"disabled": { type: Number, required: true, ...{ required: false } },'
'"disabled": { type: Number, ...{ required: false } },'
)
expect(content).toMatch(
'emits: ["update:modelValue", "update:count", "update:disabled"]'
Expand Down Expand Up @@ -1838,11 +1835,11 @@ const emit = defineEmits(['a', 'b'])
)
assertCode(content)
expect(content).toMatch('"modelValue": Boolean')
expect(content).toMatch('"fn": { }')
expect(content).toMatch('"fn": {}')
expect(content).toMatch(
'"fnWithDefault": { type: Function, ...{ default: () => null } },'
)
expect(content).toMatch('"str": { }')
expect(content).toMatch('"str": {}')
expect(content).toMatch('"optional": { required: false }')
expect(content).toMatch(
'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]'
Expand Down
22 changes: 8 additions & 14 deletions packages/compiler-sfc/src/compileScript.ts
Expand Up @@ -127,7 +127,7 @@ export interface SFCScriptCompileOptions {
*/
hoistStatic?: boolean
/**
* (Experimental) Enable macro `defineModel`
* (**Experimental**) Enable macro `defineModel`
*/
defineModel?: boolean
}
Expand Down Expand Up @@ -1032,34 +1032,28 @@ export function compileScript(
el => el === 'Boolean' || (el === 'Function' && options)
)
}
const runtimeType =
let runtimeType =
(runtimeTypes &&
runtimeTypes.length > 0 &&
toRuntimeTypeString(runtimeTypes)) ||
undefined

let decl: string

if (runtimeType && isProd && !options) {
decl = runtimeType
} else {
const pairs: string[] = []
if (runtimeType) pairs.push(`type: ${runtimeType}`)
if (!isProd) pairs.push('required: true')
decl = pairs.join(', ')

if (decl && options) {
if (runtimeType) runtimeType = `type: ${runtimeType}`
if (runtimeType && options) {
decl = isTS
? `{ ${decl}, ...${options} }`
: `Object.assign({ ${decl} }, ${options})`
? `{ ${runtimeType}, ...${options} }`
: `Object.assign({ ${runtimeType} }, ${options})`
} else {
decl = options || `{ ${decl} }`
decl = options || (runtimeType ? `{ ${runtimeType} }` : '{}')
}
}

modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`
}
return `{${modelPropsDecl}\n }`
return `${helper('addRequiredToModels')}({${modelPropsDecl}\n })`
}

let propsDecls: undefined | string
Expand Down
97 changes: 59 additions & 38 deletions packages/runtime-core/__tests__/apiSetupHelpers.spec.ts
Expand Up @@ -28,7 +28,8 @@ import {
withAsyncContext,
createPropsRestProxy,
mergeModelsOptions,
useModel
useModel,
addRequiredToModels
} from '../src/apiSetupHelpers'

describe('SFC <script setup> helpers', () => {
Expand Down Expand Up @@ -178,24 +179,40 @@ describe('SFC <script setup> helpers', () => {
})
})

test('addRequiredToModels', () => {
expect(
addRequiredToModels({
foo: {},
bar: { required: false },
baz: { default: 1 },
qux: { required: false, default: 1 },
quux: { type: String }
})
).toStrictEqual({
foo: { required: true },
bar: { required: false },
baz: { default: 1 },
qux: { required: false, default: 1 },
quux: { type: String, required: true }
})
})

describe('useModel', () => {
test('basic', async () => {
let proxy: any
let foo: any
const update = () => {
foo.value = 'bar'
}

const Comp = defineComponent({
props: {
modelValue: { required: true }
},
emits: ['update:modelValue'],
setup() {
const foo = useModel<string>('modelValue')
const update = () => {
foo.value = 'bar'
}
return { foo, update }
foo = useModel<string>('modelValue')
},
render() {
proxy = this
}
render() {}
})

const msg = ref('')
Expand All @@ -208,82 +225,86 @@ describe('SFC <script setup> helpers', () => {
})
).mount(root)

expect(proxy.foo).toBe('')
// it's a ComputedRef, so not passive
expect(foo.effect).not.toBeUndefined()

expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()

// update from child
proxy.update()
update()

await nextTick()
expect(msg.value).toBe('bar')
expect(proxy.foo).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)

// update from parent
msg.value = 'qux'

await nextTick()
expect(msg.value).toBe('qux')
expect(proxy.foo).toBe('qux')
expect(foo.value).toBe('qux')
expect(setValue).toBeCalledTimes(1)
})

test('optional', async () => {
let proxy: any
let foo: any
const update = () => {
foo.value = 'bar'
}

const Comp = defineComponent({
props: ['foo'],
emits: ['update:foo'],
setup() {
const foo = useModel<string>('foo')
const update = () => {
foo.value = 'bar'
}
return { foo, update }
foo = useModel<string>('foo')
},
render() {
proxy = this
}
render() {}
})

const root = nodeOps.createElement('div')
const updateFoo = vi.fn()
render(h(Comp, { 'onUpdate:foo': updateFoo }), root)

expect(proxy.foo).toBeUndefined()
proxy.update()
// it's a ComputedRef, so it's passive
expect(foo.effect).toBeUndefined()

expect(foo.value).toBeUndefined()
update()

expect(proxy.foo).toBe('bar')
expect(foo.value).toBe('bar')

await nextTick()
expect(updateFoo).toBeCalledTimes(1)
})

test('default value', async () => {
let proxy: any
let count: any
const inc = () => {
count.value++
}
const Comp = defineComponent({
props: { count: { default: 0 } },
emits: ['update:count'],
setup() {
const count = useModel<number>('count')
const inc = () => {
count.value++
}
return { count, inc }
count = useModel<number>('count')
},
render() {
proxy = this
}
render() {}
})

const root = nodeOps.createElement('div')
const updateCount = vi.fn()
render(h(Comp, { 'onUpdate:count': updateCount }), root)

expect(proxy.count).toBe(0)
// it's a ComputedRef, so it's passive
expect(count.effect).toBeUndefined()

expect(count.value).toBe(0)

proxy.inc()
expect(proxy.count).toBe(1)
inc()
expect(count.value).toBe(1)
await nextTick()
expect(updateCount).toBeCalledTimes(1)
})
Expand Down

0 comments on commit e56ffa8

Please sign in to comment.