compileScriptPropsTransform.spec.ts 8.4 KB

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