defineModel.spec.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { assertCode, compileSFCScript as compile } from '../utils'
  3. describe('defineModel()', () => {
  4. test('basic usage', () => {
  5. const { content, bindings } = compile(
  6. `
  7. <script setup>
  8. const modelValue = defineModel({ required: true })
  9. const c = defineModel('count')
  10. const toString = defineModel('toString', { type: Function })
  11. </script>
  12. `,
  13. )
  14. assertCode(content)
  15. expect(content).toMatch('props: {')
  16. expect(content).toMatch('"modelValue": { required: true },')
  17. expect(content).toMatch('"count": {},')
  18. expect(content).toMatch('"toString": { type: Function },')
  19. expect(content).toMatch(
  20. 'emits: ["update:modelValue", "update:count", "update:toString"],',
  21. )
  22. expect(content).toMatch(
  23. `const modelValue = _useModel(__props, "modelValue")`,
  24. )
  25. expect(content).toMatch(`const c = _useModel(__props, 'count')`)
  26. expect(content).toMatch(`const toString = _useModel(__props, 'toString')`)
  27. expect(content).toMatch(`return { modelValue, c, toString }`)
  28. expect(content).not.toMatch('defineModel')
  29. expect(bindings).toStrictEqual({
  30. modelValue: BindingTypes.SETUP_REF,
  31. count: BindingTypes.PROPS,
  32. c: BindingTypes.SETUP_REF,
  33. toString: BindingTypes.SETUP_REF,
  34. })
  35. })
  36. test('w/ template literal name', () => {
  37. const { content, bindings } = compile(
  38. `
  39. <script setup>
  40. const x = defineModel(\`x\`, { default: 100 })
  41. const y = defineModel(\`y\`, { default: 200 })
  42. </script>
  43. `,
  44. )
  45. assertCode(content)
  46. expect(content).toMatch('"x": { default: 100 },')
  47. expect(content).toMatch('"y": { default: 200 },')
  48. expect(content).toMatch('emits: ["update:x", "update:y"],')
  49. expect(content).toMatch('const x = _useModel(__props, `x`)')
  50. expect(content).toMatch('const y = _useModel(__props, `y`)')
  51. expect(content).not.toMatch('defineModel')
  52. expect(bindings).toStrictEqual({
  53. x: BindingTypes.SETUP_REF,
  54. y: BindingTypes.SETUP_REF,
  55. })
  56. })
  57. test('w/ template literal name with expressions falls back to modelValue', () => {
  58. const { content } = compile(
  59. `
  60. <script setup>
  61. const name = 'x'
  62. const m = defineModel(\`\${name}\`)
  63. </script>
  64. `,
  65. )
  66. assertCode(content)
  67. expect(content).toMatch('"modelValue":')
  68. expect(content).toMatch('_useModel(__props, "modelValue",')
  69. })
  70. test('w/ defineProps and defineEmits', () => {
  71. const { content, bindings } = compile(
  72. `
  73. <script setup>
  74. defineProps({ foo: String })
  75. defineEmits(['change'])
  76. const count = defineModel({ default: 0 })
  77. </script>
  78. `,
  79. )
  80. assertCode(content)
  81. expect(content).toMatch(`props: /*@__PURE__*/_mergeModels({ foo: String }`)
  82. expect(content).toMatch(`"modelValue": { default: 0 }`)
  83. expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
  84. expect(content).not.toMatch('defineModel')
  85. expect(bindings).toStrictEqual({
  86. count: BindingTypes.SETUP_REF,
  87. foo: BindingTypes.PROPS,
  88. modelValue: BindingTypes.PROPS,
  89. })
  90. })
  91. test('w/ array props', () => {
  92. const { content, bindings } = compile(
  93. `
  94. <script setup>
  95. defineProps(['foo', 'bar'])
  96. const count = defineModel('count')
  97. </script>
  98. `,
  99. )
  100. assertCode(content)
  101. expect(content).toMatch(`props: /*@__PURE__*/_mergeModels(['foo', 'bar'], {
  102. "count": {},
  103. "countModifiers": {},
  104. })`)
  105. expect(content).toMatch(`const count = _useModel(__props, 'count')`)
  106. expect(content).not.toMatch('defineModel')
  107. expect(bindings).toStrictEqual({
  108. foo: BindingTypes.PROPS,
  109. bar: BindingTypes.PROPS,
  110. count: BindingTypes.SETUP_REF,
  111. })
  112. })
  113. test('w/ types, basic usage', () => {
  114. const { content, bindings } = compile(
  115. `
  116. <script setup lang="ts">
  117. const modelValue = defineModel<boolean | string>()
  118. const count = defineModel<number>('count')
  119. const disabled = defineModel<number>('disabled', { required: false })
  120. const any = defineModel<any | boolean>('any')
  121. </script>
  122. `,
  123. )
  124. assertCode(content)
  125. expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
  126. expect(content).toMatch('"modelModifiers": {}')
  127. expect(content).toMatch('"count": { type: Number }')
  128. expect(content).toMatch(
  129. '"disabled": { type: Number, ...{ required: false } }',
  130. )
  131. expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
  132. expect(content).toMatch(
  133. 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]',
  134. )
  135. expect(content).toMatch(
  136. `const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
  137. )
  138. expect(content).toMatch(`const count = _useModel<number>(__props, 'count')`)
  139. expect(content).toMatch(
  140. `const disabled = _useModel<number>(__props, 'disabled')`,
  141. )
  142. expect(content).toMatch(
  143. `const any = _useModel<any | boolean>(__props, 'any')`,
  144. )
  145. expect(bindings).toStrictEqual({
  146. modelValue: BindingTypes.SETUP_REF,
  147. count: BindingTypes.SETUP_REF,
  148. disabled: BindingTypes.SETUP_REF,
  149. any: BindingTypes.SETUP_REF,
  150. })
  151. })
  152. test('w/ types, production mode', () => {
  153. const { content, bindings } = compile(
  154. `
  155. <script setup lang="ts">
  156. const modelValue = defineModel<boolean>()
  157. const fn = defineModel<() => void>('fn')
  158. const fnWithDefault = defineModel<() => void>('fnWithDefault', { default: () => null })
  159. const str = defineModel<string>('str')
  160. const optional = defineModel<string>('optional', { required: false })
  161. </script>
  162. `,
  163. { isProd: true },
  164. )
  165. assertCode(content)
  166. expect(content).toMatch('"modelValue": { type: Boolean }')
  167. expect(content).toMatch('"fn": {}')
  168. expect(content).toMatch(
  169. '"fnWithDefault": { type: Function, ...{ default: () => null } },',
  170. )
  171. expect(content).toMatch('"str": {}')
  172. expect(content).toMatch('"optional": { required: false }')
  173. expect(content).toMatch(
  174. 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
  175. )
  176. expect(content).toMatch(
  177. `const modelValue = _useModel<boolean>(__props, "modelValue")`,
  178. )
  179. expect(content).toMatch(`const fn = _useModel<() => void>(__props, 'fn')`)
  180. expect(content).toMatch(`const str = _useModel<string>(__props, 'str')`)
  181. expect(bindings).toStrictEqual({
  182. modelValue: BindingTypes.SETUP_REF,
  183. fn: BindingTypes.SETUP_REF,
  184. fnWithDefault: BindingTypes.SETUP_REF,
  185. str: BindingTypes.SETUP_REF,
  186. optional: BindingTypes.SETUP_REF,
  187. })
  188. })
  189. test('w/ types, production mode, boolean + multiple types', () => {
  190. const { content } = compile(
  191. `
  192. <script setup lang="ts">
  193. const modelValue = defineModel<boolean | string | {}>()
  194. </script>
  195. `,
  196. { isProd: true },
  197. )
  198. assertCode(content)
  199. expect(content).toMatch('"modelValue": { type: [Boolean, String, Object] }')
  200. })
  201. test('w/ types, production mode, function + runtime opts + multiple types', () => {
  202. const { content } = compile(
  203. `
  204. <script setup lang="ts">
  205. const modelValue = defineModel<number | (() => number)>({ default: () => 1 })
  206. </script>
  207. `,
  208. { isProd: true },
  209. )
  210. assertCode(content)
  211. expect(content).toMatch(
  212. '"modelValue": { type: [Number, Function], ...{ default: () => 1 } }',
  213. )
  214. })
  215. test('get / set transformers', () => {
  216. const { content } = compile(
  217. `
  218. <script setup lang="ts">
  219. const modelValue = defineModel({
  220. get(v) { return v - 1 },
  221. set: (v) => { return v + 1 },
  222. required: true
  223. })
  224. </script>
  225. `,
  226. )
  227. assertCode(content)
  228. expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
  229. expect(content).toMatch(
  230. `_useModel(__props, "modelValue", {
  231. get(v) { return v - 1 },
  232. set: (v) => { return v + 1 },
  233. })`,
  234. )
  235. const { content: content2 } = compile(
  236. `
  237. <script setup lang="ts">
  238. const modelValue = defineModel({
  239. default: 0,
  240. get(v) { return v - 1 },
  241. required: true,
  242. set: (v) => { return v + 1 },
  243. })
  244. </script>
  245. `,
  246. )
  247. assertCode(content2)
  248. expect(content2).toMatch(
  249. /"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
  250. )
  251. expect(content2).toMatch(
  252. `_useModel(__props, "modelValue", {
  253. get(v) { return v - 1 },
  254. set: (v) => { return v + 1 },
  255. })`,
  256. )
  257. })
  258. test('usage w/ props destructure', () => {
  259. const { content } = compile(
  260. `
  261. <script setup lang="ts">
  262. const { x } = defineProps<{ x: number }>()
  263. const modelValue = defineModel({
  264. set: (v) => { return v + x }
  265. })
  266. </script>
  267. `,
  268. { propsDestructure: true },
  269. )
  270. assertCode(content)
  271. expect(content).toMatch(`set: (v) => { return v + __props.x }`)
  272. })
  273. test('w/ Boolean And Function types, production mode', () => {
  274. const { content, bindings } = compile(
  275. `
  276. <script setup lang="ts">
  277. const modelValue = defineModel<boolean | string>()
  278. </script>
  279. `,
  280. { isProd: true },
  281. )
  282. assertCode(content)
  283. expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
  284. expect(content).toMatch('emits: ["update:modelValue"]')
  285. expect(content).toMatch(
  286. `const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
  287. )
  288. expect(bindings).toStrictEqual({
  289. modelValue: BindingTypes.SETUP_REF,
  290. })
  291. })
  292. })