compileScriptRefSugar.spec.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. import { BindingTypes } from '@vue/compiler-core'
  2. import { compileSFCScript as compile, assertCode } from './utils'
  3. describe('<script setup> ref sugar', () => {
  4. function compileWithRefSugar(src: string) {
  5. return compile(src, { refSugar: true })
  6. }
  7. test('$ref declarations', () => {
  8. const { content, bindings } = compileWithRefSugar(`<script setup>
  9. let foo = $ref()
  10. let a = $ref(1)
  11. let b = $ref({
  12. count: 0
  13. })
  14. let c = () => {}
  15. let d
  16. </script>`)
  17. expect(content).toMatch(`import { ref as _ref } from 'vue'`)
  18. expect(content).not.toMatch(`$ref()`)
  19. expect(content).not.toMatch(`$ref(1)`)
  20. expect(content).not.toMatch(`$ref({`)
  21. expect(content).toMatch(`let foo = _ref()`)
  22. expect(content).toMatch(`let a = _ref(1)`)
  23. expect(content).toMatch(`
  24. let b = _ref({
  25. count: 0
  26. })
  27. `)
  28. // normal declarations left untouched
  29. expect(content).toMatch(`let c = () => {}`)
  30. expect(content).toMatch(`let d`)
  31. assertCode(content)
  32. expect(bindings).toStrictEqual({
  33. foo: BindingTypes.SETUP_REF,
  34. a: BindingTypes.SETUP_REF,
  35. b: BindingTypes.SETUP_REF,
  36. c: BindingTypes.SETUP_LET,
  37. d: BindingTypes.SETUP_LET
  38. })
  39. })
  40. test('multi $ref declarations', () => {
  41. const { content, bindings } = compileWithRefSugar(`<script setup>
  42. let a = $ref(1), b = $ref(2), c = $ref({
  43. count: 0
  44. })
  45. </script>`)
  46. expect(content).toMatch(`
  47. let a = _ref(1), b = _ref(2), c = _ref({
  48. count: 0
  49. })
  50. `)
  51. expect(content).toMatch(`return { a, b, c }`)
  52. assertCode(content)
  53. expect(bindings).toStrictEqual({
  54. a: BindingTypes.SETUP_REF,
  55. b: BindingTypes.SETUP_REF,
  56. c: BindingTypes.SETUP_REF
  57. })
  58. })
  59. test('$computed declaration', () => {
  60. const { content, bindings } = compileWithRefSugar(`<script setup>
  61. const a = $computed(() => 1)
  62. </script>`)
  63. expect(content).toMatch(`
  64. const a = _computed(() => 1)
  65. `)
  66. expect(content).toMatch(`return { a }`)
  67. assertCode(content)
  68. expect(bindings).toStrictEqual({
  69. a: BindingTypes.SETUP_REF
  70. })
  71. })
  72. test('mixing $ref & $computed declarations', () => {
  73. const { content, bindings } = compileWithRefSugar(`<script setup>
  74. let a = $ref(1), b = $computed(() => a + 1)
  75. </script>`)
  76. expect(content).toMatch(`
  77. let a = _ref(1), b = _computed(() => a.value + 1)
  78. `)
  79. expect(content).toMatch(`return { a, b }`)
  80. assertCode(content)
  81. expect(bindings).toStrictEqual({
  82. a: BindingTypes.SETUP_REF,
  83. b: BindingTypes.SETUP_REF
  84. })
  85. })
  86. test('accessing ref binding', () => {
  87. const { content } = compileWithRefSugar(`<script setup>
  88. let a = $ref(1)
  89. console.log(a)
  90. function get() {
  91. return a + 1
  92. }
  93. </script>`)
  94. expect(content).toMatch(`console.log(a.value)`)
  95. expect(content).toMatch(`return a.value + 1`)
  96. assertCode(content)
  97. })
  98. test('cases that should not append .value', () => {
  99. const { content } = compileWithRefSugar(`<script setup>
  100. let a = $ref(1)
  101. console.log(b.a)
  102. function get(a) {
  103. return a + 1
  104. }
  105. </script>`)
  106. expect(content).not.toMatch(`a.value`)
  107. })
  108. test('mutating ref binding', () => {
  109. const { content } = compileWithRefSugar(`<script setup>
  110. let a = $ref(1)
  111. let b = $ref({ count: 0 })
  112. function inc() {
  113. a++
  114. a = a + 1
  115. b.count++
  116. b.count = b.count + 1
  117. ;({ a } = { a: 2 })
  118. ;[a] = [1]
  119. }
  120. </script>`)
  121. expect(content).toMatch(`a.value++`)
  122. expect(content).toMatch(`a.value = a.value + 1`)
  123. expect(content).toMatch(`b.value.count++`)
  124. expect(content).toMatch(`b.value.count = b.value.count + 1`)
  125. expect(content).toMatch(`;({ a: a.value } = { a: 2 })`)
  126. expect(content).toMatch(`;[a.value] = [1]`)
  127. assertCode(content)
  128. })
  129. test('using ref binding in property shorthand', () => {
  130. const { content } = compileWithRefSugar(`<script setup>
  131. let a = $ref(1)
  132. const b = { a }
  133. function test() {
  134. const { a } = b
  135. }
  136. </script>`)
  137. expect(content).toMatch(`const b = { a: a.value }`)
  138. // should not convert destructure
  139. expect(content).toMatch(`const { a } = b`)
  140. assertCode(content)
  141. })
  142. test('should not rewrite scope variable', () => {
  143. const { content } = compileWithRefSugar(`
  144. <script setup>
  145. let a = $ref(1)
  146. let b = $ref(1)
  147. let d = $ref(1)
  148. const e = 1
  149. function test() {
  150. const a = 2
  151. console.log(a)
  152. console.log(b)
  153. let c = { c: 3 }
  154. console.log(c)
  155. console.log(d)
  156. console.log(e)
  157. }
  158. </script>`)
  159. expect(content).toMatch('console.log(a)')
  160. expect(content).toMatch('console.log(b.value)')
  161. expect(content).toMatch('console.log(c)')
  162. expect(content).toMatch('console.log(d.value)')
  163. expect(content).toMatch('console.log(e)')
  164. assertCode(content)
  165. })
  166. test('object destructure', () => {
  167. const { content, bindings } = compileWithRefSugar(`<script setup>
  168. let n = $ref(1), { a, b: c, d = 1, e: f = 2, ...g } = $fromRefs(useFoo())
  169. let { foo } = $fromRefs(useSomthing(() => 1));
  170. console.log(n, a, c, d, f, g, foo)
  171. </script>`)
  172. expect(content).toMatch(
  173. `let n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = (useFoo())`
  174. )
  175. expect(content).toMatch(`let { foo: __foo } = (useSomthing(() => 1))`)
  176. expect(content).toMatch(`\nconst a = _ref(__a);`)
  177. expect(content).not.toMatch(`\nconst b = _ref(__b);`)
  178. expect(content).toMatch(`\nconst c = _ref(__c);`)
  179. expect(content).toMatch(`\nconst d = _ref(__d);`)
  180. expect(content).not.toMatch(`\nconst e = _ref(__e);`)
  181. expect(content).toMatch(`\nconst f = _ref(__f);`)
  182. expect(content).toMatch(`\nconst g = _ref(__g);`)
  183. expect(content).toMatch(`\nconst foo = _ref(__foo);`)
  184. expect(content).toMatch(
  185. `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
  186. )
  187. expect(content).toMatch(`return { n, a, c, d, f, g, foo }`)
  188. expect(bindings).toStrictEqual({
  189. n: BindingTypes.SETUP_REF,
  190. a: BindingTypes.SETUP_REF,
  191. c: BindingTypes.SETUP_REF,
  192. d: BindingTypes.SETUP_REF,
  193. f: BindingTypes.SETUP_REF,
  194. g: BindingTypes.SETUP_REF,
  195. foo: BindingTypes.SETUP_REF
  196. })
  197. assertCode(content)
  198. })
  199. test('array destructure', () => {
  200. const { content, bindings } = compileWithRefSugar(`<script setup>
  201. let n = $ref(1), [a, b = 1, ...c] = $fromRefs(useFoo())
  202. console.log(n, a, b, c)
  203. </script>`)
  204. expect(content).toMatch(
  205. `let n = _ref(1), [__a, __b = 1, ...__c] = (useFoo())`
  206. )
  207. expect(content).toMatch(`\nconst a = _ref(__a);`)
  208. expect(content).toMatch(`\nconst b = _ref(__b);`)
  209. expect(content).toMatch(`\nconst c = _ref(__c);`)
  210. expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
  211. expect(content).toMatch(`return { n, a, b, c }`)
  212. expect(bindings).toStrictEqual({
  213. n: BindingTypes.SETUP_REF,
  214. a: BindingTypes.SETUP_REF,
  215. b: BindingTypes.SETUP_REF,
  216. c: BindingTypes.SETUP_REF
  217. })
  218. assertCode(content)
  219. })
  220. test('nested destructure', () => {
  221. const { content, bindings } = compileWithRefSugar(`<script setup>
  222. let [{ a: { b }}] = $fromRefs(useFoo())
  223. let { c: [d, e] } = $fromRefs(useBar())
  224. console.log(b, d, e)
  225. </script>`)
  226. expect(content).toMatch(`let [{ a: { b: __b }}] = (useFoo())`)
  227. expect(content).toMatch(`let { c: [__d, __e] } = (useBar())`)
  228. expect(content).not.toMatch(`\nconst a = _ref(__a);`)
  229. expect(content).not.toMatch(`\nconst c = _ref(__c);`)
  230. expect(content).toMatch(`\nconst b = _ref(__b);`)
  231. expect(content).toMatch(`\nconst d = _ref(__d);`)
  232. expect(content).toMatch(`\nconst e = _ref(__e);`)
  233. expect(content).toMatch(`return { b, d, e }`)
  234. expect(bindings).toStrictEqual({
  235. b: BindingTypes.SETUP_REF,
  236. d: BindingTypes.SETUP_REF,
  237. e: BindingTypes.SETUP_REF
  238. })
  239. assertCode(content)
  240. })
  241. test('$raw', () => {
  242. const { content } = compileWithRefSugar(`<script setup>
  243. let a = $ref(1)
  244. const b = $raw(a)
  245. const c = $raw({ a })
  246. callExternal($raw(a))
  247. </script>`)
  248. expect(content).toMatch(`const b = (a)`)
  249. expect(content).toMatch(`const c = ({ a })`)
  250. expect(content).toMatch(`callExternal((a))`)
  251. assertCode(content)
  252. })
  253. //#4062
  254. test('should not rewrite type identifiers', () => {
  255. const { content } = compile(
  256. `
  257. <script setup lang="ts">
  258. const props = defineProps<{msg: string; ids?: string[]}>()
  259. let ids = $ref([])
  260. </script>`,
  261. {
  262. refSugar: true
  263. }
  264. )
  265. assertCode(content)
  266. expect(content).not.toMatch('.value')
  267. })
  268. describe('errors', () => {
  269. test('non-let $ref declaration', () => {
  270. expect(() =>
  271. compile(
  272. `<script setup>
  273. const a = $ref(1)
  274. </script>`,
  275. { refSugar: true }
  276. )
  277. ).toThrow(`$ref() bindings can only be declared with let`)
  278. })
  279. test('$ref w/ destructure', () => {
  280. expect(() =>
  281. compile(
  282. `<script setup>
  283. let { a } = $ref(1)
  284. </script>`,
  285. { refSugar: true }
  286. )
  287. ).toThrow(`$ref() bindings cannot be used with destructuring`)
  288. })
  289. test('$computed w/ destructure', () => {
  290. expect(() =>
  291. compile(
  292. `<script setup>
  293. const { a } = $computed(() => 1)
  294. </script>`,
  295. { refSugar: true }
  296. )
  297. ).toThrow(`$computed() bindings cannot be used with destructuring`)
  298. })
  299. test('defineProps/Emit() referencing ref declarations', () => {
  300. expect(() =>
  301. compile(
  302. `<script setup>
  303. let bar = $ref(1)
  304. defineProps({
  305. bar
  306. })
  307. </script>`,
  308. { refSugar: true }
  309. )
  310. ).toThrow(`cannot reference locally declared variables`)
  311. expect(() =>
  312. compile(
  313. `<script setup>
  314. let bar = $ref(1)
  315. defineEmits({
  316. bar
  317. })
  318. </script>`,
  319. { refSugar: true }
  320. )
  321. ).toThrow(`cannot reference locally declared variables`)
  322. })
  323. })
  324. })