compileScriptRefSugar.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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 = _shallowRef(__a);`)
  177. expect(content).not.toMatch(`\nconst b = _shallowRef(__b);`)
  178. expect(content).toMatch(`\nconst c = _shallowRef(__c);`)
  179. expect(content).toMatch(`\nconst d = _shallowRef(__d);`)
  180. expect(content).not.toMatch(`\nconst e = _shallowRef(__e);`)
  181. expect(content).toMatch(`\nconst f = _shallowRef(__f);`)
  182. expect(content).toMatch(`\nconst g = _shallowRef(__g);`)
  183. expect(content).toMatch(`\nconst foo = _shallowRef(__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 = _shallowRef(__a);`)
  208. expect(content).toMatch(`\nconst b = _shallowRef(__b);`)
  209. expect(content).toMatch(`\nconst c = _shallowRef(__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 = _shallowRef(__a);`)
  229. expect(content).not.toMatch(`\nconst c = _shallowRef(__c);`)
  230. expect(content).toMatch(`\nconst b = _shallowRef(__b);`)
  231. expect(content).toMatch(`\nconst d = _shallowRef(__d);`)
  232. expect(content).toMatch(`\nconst e = _shallowRef(__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. // #4254
  269. test('handle TS casting syntax', () => {
  270. const { content } = compile(
  271. `
  272. <script setup lang="ts">
  273. let a = $ref(1)
  274. console.log(a!)
  275. console.log(a! + 1)
  276. console.log(a as number)
  277. console.log((a as number) + 1)
  278. console.log(<number>a)
  279. console.log(<number>a + 1)
  280. console.log(a! + (a as number))
  281. console.log(a! + <number>a)
  282. console.log((a as number) + <number>a)
  283. </script>`,
  284. {
  285. refSugar: true
  286. }
  287. )
  288. assertCode(content)
  289. expect(content).toMatch('console.log(a.value!)')
  290. expect(content).toMatch('console.log(a.value as number)')
  291. expect(content).toMatch('console.log(<number>a.value)')
  292. })
  293. describe('errors', () => {
  294. test('non-let $ref declaration', () => {
  295. expect(() =>
  296. compile(
  297. `<script setup>
  298. const a = $ref(1)
  299. </script>`,
  300. { refSugar: true }
  301. )
  302. ).toThrow(`$ref() bindings can only be declared with let`)
  303. })
  304. test('$ref w/ destructure', () => {
  305. expect(() =>
  306. compile(
  307. `<script setup>
  308. let { a } = $ref(1)
  309. </script>`,
  310. { refSugar: true }
  311. )
  312. ).toThrow(`$ref() bindings cannot be used with destructuring`)
  313. })
  314. test('$computed w/ destructure', () => {
  315. expect(() =>
  316. compile(
  317. `<script setup>
  318. const { a } = $computed(() => 1)
  319. </script>`,
  320. { refSugar: true }
  321. )
  322. ).toThrow(`$computed() bindings cannot be used with destructuring`)
  323. })
  324. test('defineProps/Emit() referencing ref declarations', () => {
  325. expect(() =>
  326. compile(
  327. `<script setup>
  328. let bar = $ref(1)
  329. defineProps({
  330. bar
  331. })
  332. </script>`,
  333. { refSugar: true }
  334. )
  335. ).toThrow(`cannot reference locally declared variables`)
  336. expect(() =>
  337. compile(
  338. `<script setup>
  339. let bar = $ref(1)
  340. defineEmits({
  341. bar
  342. })
  343. </script>`,
  344. { refSugar: true }
  345. )
  346. ).toThrow(`cannot reference locally declared variables`)
  347. })
  348. })
  349. })