defineModel.spec.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  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/ defineProps and defineEmits', () => {
  37. const { content, bindings } = compile(
  38. `
  39. <script setup>
  40. defineProps({ foo: String })
  41. defineEmits(['change'])
  42. const count = defineModel({ default: 0 })
  43. </script>
  44. `,
  45. )
  46. assertCode(content)
  47. expect(content).toMatch(`props: /*@__PURE__*/_mergeModels({ foo: String }`)
  48. expect(content).toMatch(`"modelValue": { default: 0 }`)
  49. expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
  50. expect(content).not.toMatch('defineModel')
  51. expect(bindings).toStrictEqual({
  52. count: BindingTypes.SETUP_REF,
  53. foo: BindingTypes.PROPS,
  54. modelValue: BindingTypes.PROPS,
  55. })
  56. })
  57. test('w/ array props', () => {
  58. const { content, bindings } = compile(
  59. `
  60. <script setup>
  61. defineProps(['foo', 'bar'])
  62. const count = defineModel('count')
  63. </script>
  64. `,
  65. )
  66. assertCode(content)
  67. expect(content).toMatch(`props: /*@__PURE__*/_mergeModels(['foo', 'bar'], {
  68. "count": {},
  69. "countModifiers": {},
  70. })`)
  71. expect(content).toMatch(`const count = _useModel(__props, 'count')`)
  72. expect(content).not.toMatch('defineModel')
  73. expect(bindings).toStrictEqual({
  74. foo: BindingTypes.PROPS,
  75. bar: BindingTypes.PROPS,
  76. count: BindingTypes.SETUP_REF,
  77. })
  78. })
  79. test('w/ types, basic usage', () => {
  80. const { content, bindings } = compile(
  81. `
  82. <script setup lang="ts">
  83. const modelValue = defineModel<boolean | string>()
  84. const count = defineModel<number>('count')
  85. const disabled = defineModel<number>('disabled', { required: false })
  86. const any = defineModel<any | boolean>('any')
  87. </script>
  88. `,
  89. )
  90. assertCode(content)
  91. expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
  92. expect(content).toMatch('"modelModifiers": {}')
  93. expect(content).toMatch('"count": { type: Number }')
  94. expect(content).toMatch(
  95. '"disabled": { type: Number, ...{ required: false } }',
  96. )
  97. expect(content).toMatch('"any": { type: Boolean, skipCheck: true }')
  98. expect(content).toMatch(
  99. 'emits: ["update:modelValue", "update:count", "update:disabled", "update:any"]',
  100. )
  101. expect(content).toMatch(
  102. `const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
  103. )
  104. expect(content).toMatch(`const count = _useModel<number>(__props, 'count')`)
  105. expect(content).toMatch(
  106. `const disabled = _useModel<number>(__props, 'disabled')`,
  107. )
  108. expect(content).toMatch(
  109. `const any = _useModel<any | boolean>(__props, 'any')`,
  110. )
  111. expect(bindings).toStrictEqual({
  112. modelValue: BindingTypes.SETUP_REF,
  113. count: BindingTypes.SETUP_REF,
  114. disabled: BindingTypes.SETUP_REF,
  115. any: BindingTypes.SETUP_REF,
  116. })
  117. })
  118. test('w/ types, production mode', () => {
  119. const { content, bindings } = compile(
  120. `
  121. <script setup lang="ts">
  122. const modelValue = defineModel<boolean>()
  123. const fn = defineModel<() => void>('fn')
  124. const fnWithDefault = defineModel<() => void>('fnWithDefault', { default: () => null })
  125. const str = defineModel<string>('str')
  126. const optional = defineModel<string>('optional', { required: false })
  127. </script>
  128. `,
  129. { isProd: true },
  130. )
  131. assertCode(content)
  132. expect(content).toMatch('"modelValue": { type: Boolean }')
  133. expect(content).toMatch('"fn": {}')
  134. expect(content).toMatch(
  135. '"fnWithDefault": { type: Function, ...{ default: () => null } },',
  136. )
  137. expect(content).toMatch('"str": {}')
  138. expect(content).toMatch('"optional": { required: false }')
  139. expect(content).toMatch(
  140. 'emits: ["update:modelValue", "update:fn", "update:fnWithDefault", "update:str", "update:optional"]',
  141. )
  142. expect(content).toMatch(
  143. `const modelValue = _useModel<boolean>(__props, "modelValue")`,
  144. )
  145. expect(content).toMatch(`const fn = _useModel<() => void>(__props, 'fn')`)
  146. expect(content).toMatch(`const str = _useModel<string>(__props, 'str')`)
  147. expect(bindings).toStrictEqual({
  148. modelValue: BindingTypes.SETUP_REF,
  149. fn: BindingTypes.SETUP_REF,
  150. fnWithDefault: BindingTypes.SETUP_REF,
  151. str: BindingTypes.SETUP_REF,
  152. optional: BindingTypes.SETUP_REF,
  153. })
  154. })
  155. test('w/ types, production mode, boolean + multiple types', () => {
  156. const { content } = compile(
  157. `
  158. <script setup lang="ts">
  159. const modelValue = defineModel<boolean | string | {}>()
  160. </script>
  161. `,
  162. { isProd: true },
  163. )
  164. assertCode(content)
  165. expect(content).toMatch('"modelValue": { type: [Boolean, String, Object] }')
  166. })
  167. test('w/ types, production mode, function + runtime opts + multiple types', () => {
  168. const { content } = compile(
  169. `
  170. <script setup lang="ts">
  171. const modelValue = defineModel<number | (() => number)>({ default: () => 1 })
  172. </script>
  173. `,
  174. { isProd: true },
  175. )
  176. assertCode(content)
  177. expect(content).toMatch(
  178. '"modelValue": { type: [Number, Function], ...{ default: () => 1 } }',
  179. )
  180. })
  181. test('get / set transformers', () => {
  182. const { content } = compile(
  183. `
  184. <script setup lang="ts">
  185. const modelValue = defineModel({
  186. get(v) { return v - 1 },
  187. set: (v) => { return v + 1 },
  188. required: true
  189. })
  190. </script>
  191. `,
  192. )
  193. assertCode(content)
  194. expect(content).toMatch(/"modelValue": {\s+required: true,?\s+}/m)
  195. expect(content).toMatch(
  196. `_useModel(__props, "modelValue", {
  197. get(v) { return v - 1 },
  198. set: (v) => { return v + 1 },
  199. })`,
  200. )
  201. const { content: content2 } = compile(
  202. `
  203. <script setup lang="ts">
  204. const modelValue = defineModel({
  205. default: 0,
  206. get(v) { return v - 1 },
  207. required: true,
  208. set: (v) => { return v + 1 },
  209. })
  210. </script>
  211. `,
  212. )
  213. assertCode(content2)
  214. expect(content2).toMatch(
  215. /"modelValue": {\s+default: 0,\s+required: true,?\s+}/m,
  216. )
  217. expect(content2).toMatch(
  218. `_useModel(__props, "modelValue", {
  219. get(v) { return v - 1 },
  220. set: (v) => { return v + 1 },
  221. })`,
  222. )
  223. })
  224. test('usage w/ props destructure', () => {
  225. const { content } = compile(
  226. `
  227. <script setup lang="ts">
  228. const { x } = defineProps<{ x: number }>()
  229. const modelValue = defineModel({
  230. set: (v) => { return v + x }
  231. })
  232. </script>
  233. `,
  234. { propsDestructure: true },
  235. )
  236. assertCode(content)
  237. expect(content).toMatch(`set: (v) => { return v + __props.x }`)
  238. })
  239. test('w/ Boolean And Function types, production mode', () => {
  240. const { content, bindings } = compile(
  241. `
  242. <script setup lang="ts">
  243. const modelValue = defineModel<boolean | string>()
  244. </script>
  245. `,
  246. { isProd: true },
  247. )
  248. assertCode(content)
  249. expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
  250. expect(content).toMatch('emits: ["update:modelValue"]')
  251. expect(content).toMatch(
  252. `const modelValue = _useModel<boolean | string>(__props, "modelValue")`,
  253. )
  254. expect(bindings).toStrictEqual({
  255. modelValue: BindingTypes.SETUP_REF,
  256. })
  257. })
  258. })