compileScriptPropsTransform.spec.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { SFCScriptCompileOptions } from '../src'
  3. import { compileSFCScript, assertCode } from './utils'
  4. describe('sfc props transform', () => {
  5. function compile(src: string, options?: Partial<SFCScriptCompileOptions>) {
  6. return compileSFCScript(src, {
  7. inlineTemplate: true,
  8. reactivityTransform: true,
  9. ...options
  10. })
  11. }
  12. test('basic usage', () => {
  13. const { content, bindings } = compile(`
  14. <script setup>
  15. const { foo } = defineProps(['foo'])
  16. console.log(foo)
  17. </script>
  18. <template>{{ foo }}</template>
  19. `)
  20. expect(content).not.toMatch(`const { foo } =`)
  21. expect(content).toMatch(`console.log(__props.foo)`)
  22. expect(content).toMatch(`_toDisplayString(__props.foo)`)
  23. assertCode(content)
  24. expect(bindings).toStrictEqual({
  25. foo: BindingTypes.PROPS
  26. })
  27. })
  28. test('nested scope', () => {
  29. const { content, bindings } = compile(`
  30. <script setup>
  31. const { foo, bar } = defineProps(['foo', 'bar'])
  32. function test(foo) {
  33. console.log(foo)
  34. console.log(bar)
  35. }
  36. </script>
  37. `)
  38. expect(content).not.toMatch(`const { foo, bar } =`)
  39. expect(content).toMatch(`console.log(foo)`)
  40. expect(content).toMatch(`console.log(__props.bar)`)
  41. assertCode(content)
  42. expect(bindings).toStrictEqual({
  43. foo: BindingTypes.PROPS,
  44. bar: BindingTypes.PROPS,
  45. test: BindingTypes.SETUP_CONST
  46. })
  47. })
  48. test('default values w/ runtime declaration', () => {
  49. const { content } = compile(`
  50. <script setup>
  51. const { foo = 1, bar = {} } = defineProps(['foo', 'bar'])
  52. </script>
  53. `)
  54. // literals can be used as-is, non-literals are always returned from a
  55. // function
  56. expect(content).toMatch(`props: _mergeDefaults(['foo', 'bar'], {
  57. foo: 1,
  58. bar: () => ({})
  59. })`)
  60. assertCode(content)
  61. })
  62. test('default values w/ type declaration', () => {
  63. const { content } = compile(`
  64. <script setup lang="ts">
  65. const { foo = 1, bar = {} } = defineProps<{ foo?: number, bar?: object }>()
  66. </script>
  67. `)
  68. // literals can be used as-is, non-literals are always returned from a
  69. // function
  70. expect(content).toMatch(`props: {
  71. foo: { type: Number, required: false, default: 1 },
  72. bar: { type: Object, required: false, default: () => ({}) }
  73. }`)
  74. assertCode(content)
  75. })
  76. test('default values w/ type declaration, prod mode', () => {
  77. const { content } = compile(
  78. `
  79. <script setup lang="ts">
  80. const { foo = 1, bar = {}, func = () => {} } = defineProps<{ foo?: number, bar?: object, baz?: any, boola?: boolean, boolb?: boolean | number, func?: Function }>()
  81. </script>
  82. `,
  83. { isProd: true }
  84. )
  85. // literals can be used as-is, non-literals are always returned from a
  86. // function
  87. expect(content).toMatch(`props: {
  88. foo: { default: 1 },
  89. bar: { default: () => ({}) },
  90. baz: null,
  91. boola: { type: Boolean },
  92. boolb: { type: [Boolean, Number] },
  93. func: { type: Function, default: () => (() => {}) }
  94. }`)
  95. assertCode(content)
  96. })
  97. test('aliasing', () => {
  98. const { content, bindings } = compile(`
  99. <script setup>
  100. const { foo: bar } = defineProps(['foo'])
  101. let x = foo
  102. let y = bar
  103. </script>
  104. <template>{{ foo + bar }}</template>
  105. `)
  106. expect(content).not.toMatch(`const { foo: bar } =`)
  107. expect(content).toMatch(`let x = foo`) // should not process
  108. expect(content).toMatch(`let y = __props.foo`)
  109. // should convert bar to __props.foo in template expressions
  110. expect(content).toMatch(`_toDisplayString(__props.foo + __props.foo)`)
  111. assertCode(content)
  112. expect(bindings).toStrictEqual({
  113. x: BindingTypes.SETUP_LET,
  114. y: BindingTypes.SETUP_LET,
  115. foo: BindingTypes.PROPS,
  116. bar: BindingTypes.PROPS_ALIASED,
  117. __propsAliases: {
  118. bar: 'foo'
  119. }
  120. })
  121. })
  122. // #5425
  123. test('non-identifier prop names', () => {
  124. const { content, bindings } = compile(`
  125. <script setup>
  126. const { 'foo.bar': fooBar } = defineProps({ 'foo.bar': Function })
  127. let x = fooBar
  128. </script>
  129. <template>{{ fooBar }}</template>
  130. `)
  131. expect(content).toMatch(`x = __props["foo.bar"]`)
  132. expect(content).toMatch(`toDisplayString(__props["foo.bar"])`)
  133. assertCode(content)
  134. expect(bindings).toStrictEqual({
  135. x: BindingTypes.SETUP_LET,
  136. 'foo.bar': BindingTypes.PROPS,
  137. fooBar: BindingTypes.PROPS_ALIASED,
  138. __propsAliases: {
  139. fooBar: 'foo.bar'
  140. }
  141. })
  142. })
  143. test('rest spread', () => {
  144. const { content, bindings } = compile(`
  145. <script setup>
  146. const { foo, bar, ...rest } = defineProps(['foo', 'bar', 'baz'])
  147. </script>
  148. `)
  149. expect(content).toMatch(
  150. `const rest = _createPropsRestProxy(__props, ["foo","bar"])`
  151. )
  152. assertCode(content)
  153. expect(bindings).toStrictEqual({
  154. foo: BindingTypes.PROPS,
  155. bar: BindingTypes.PROPS,
  156. baz: BindingTypes.PROPS,
  157. rest: BindingTypes.SETUP_REACTIVE_CONST
  158. })
  159. })
  160. test('$$() escape', () => {
  161. const { content } = compile(`
  162. <script setup>
  163. const { foo, bar: baz } = defineProps(['foo'])
  164. console.log($$(foo))
  165. console.log($$(baz))
  166. $$({ foo, baz })
  167. </script>
  168. `)
  169. expect(content).toMatch(`const __props_foo = _toRef(__props, 'foo')`)
  170. expect(content).toMatch(`const __props_bar = _toRef(__props, 'bar')`)
  171. expect(content).toMatch(`console.log((__props_foo))`)
  172. expect(content).toMatch(`console.log((__props_bar))`)
  173. expect(content).toMatch(`({ foo: __props_foo, baz: __props_bar })`)
  174. assertCode(content)
  175. })
  176. // #6960
  177. test('computed static key', () => {
  178. const { content, bindings } = compile(`
  179. <script setup>
  180. const { ['foo']: foo } = defineProps(['foo'])
  181. console.log(foo)
  182. </script>
  183. <template>{{ foo }}</template>
  184. `)
  185. expect(content).not.toMatch(`const { foo } =`)
  186. expect(content).toMatch(`console.log(__props.foo)`)
  187. expect(content).toMatch(`_toDisplayString(__props.foo)`)
  188. assertCode(content)
  189. expect(bindings).toStrictEqual({
  190. foo: BindingTypes.PROPS
  191. })
  192. })
  193. describe('errors', () => {
  194. test('should error on deep destructure', () => {
  195. expect(() =>
  196. compile(
  197. `<script setup>const { foo: [bar] } = defineProps(['foo'])</script>`
  198. )
  199. ).toThrow(`destructure does not support nested patterns`)
  200. expect(() =>
  201. compile(
  202. `<script setup>const { foo: { bar } } = defineProps(['foo'])</script>`
  203. )
  204. ).toThrow(`destructure does not support nested patterns`)
  205. })
  206. test('should error on computed key', () => {
  207. expect(() =>
  208. compile(
  209. `<script setup>const { [foo]: bar } = defineProps(['foo'])</script>`
  210. )
  211. ).toThrow(`destructure cannot use computed key`)
  212. })
  213. test('should error when used with withDefaults', () => {
  214. expect(() =>
  215. compile(
  216. `<script setup lang="ts">
  217. const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
  218. </script>`
  219. )
  220. ).toThrow(`withDefaults() is unnecessary when using destructure`)
  221. })
  222. test('should error if destructure reference local vars', () => {
  223. expect(() =>
  224. compile(
  225. `<script setup>
  226. const x = 1
  227. const {
  228. foo = () => x
  229. } = defineProps(['foo'])
  230. </script>`
  231. )
  232. ).toThrow(`cannot reference locally declared variables`)
  233. })
  234. })
  235. })