ssrWatch.spec.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import {
  2. createSSRApp,
  3. defineComponent,
  4. h,
  5. nextTick,
  6. ref,
  7. watch,
  8. watchEffect,
  9. withAsyncContext,
  10. } from 'vue'
  11. import { type SSRContext, renderToString } from '../src'
  12. describe('ssr: watch', () => {
  13. // #6013
  14. test('should work w/ flush:sync', async () => {
  15. const App = defineComponent(() => {
  16. const count = ref(0)
  17. let msg = ''
  18. watch(
  19. count,
  20. () => {
  21. msg = 'hello world'
  22. },
  23. { flush: 'sync' },
  24. )
  25. count.value = 1
  26. expect(msg).toBe('hello world')
  27. return () => h('div', null, msg)
  28. })
  29. const app = createSSRApp(App)
  30. const ctx: SSRContext = {}
  31. const html = await renderToString(app, ctx)
  32. expect(ctx.__watcherHandles!.length).toBe(0)
  33. expect(html).toMatch('hello world')
  34. })
  35. test('should work with flush: sync and immediate: true', async () => {
  36. const text = ref('start')
  37. let msg = 'unchanged'
  38. const App = defineComponent(() => {
  39. watch(
  40. text,
  41. () => {
  42. msg = text.value
  43. },
  44. { flush: 'sync', immediate: true },
  45. )
  46. expect(msg).toBe('start')
  47. text.value = 'changed'
  48. expect(msg).toBe('changed')
  49. text.value = 'changed again'
  50. expect(msg).toBe('changed again')
  51. return () => h('div', null, msg)
  52. })
  53. const app = createSSRApp(App)
  54. const ctx: SSRContext = {}
  55. const html = await renderToString(app, ctx)
  56. expect(ctx.__watcherHandles!.length).toBe(0)
  57. expect(html).toMatch('changed again')
  58. await nextTick()
  59. expect(msg).toBe('changed again')
  60. })
  61. test('should run once with immediate: true', async () => {
  62. const text = ref('start')
  63. let msg = 'unchanged'
  64. const App = defineComponent(() => {
  65. watch(
  66. text,
  67. () => {
  68. msg = String(text.value)
  69. },
  70. { immediate: true },
  71. )
  72. text.value = 'changed'
  73. expect(msg).toBe('start')
  74. return () => h('div', null, msg)
  75. })
  76. const app = createSSRApp(App)
  77. const ctx: SSRContext = {}
  78. const html = await renderToString(app, ctx)
  79. expect(ctx.__watcherHandles).toBeUndefined()
  80. expect(html).toMatch('start')
  81. await nextTick()
  82. expect(msg).toBe('start')
  83. })
  84. test('should run once with immediate: true and flush: post', async () => {
  85. const text = ref('start')
  86. let msg = 'unchanged'
  87. const App = defineComponent(() => {
  88. watch(
  89. text,
  90. () => {
  91. msg = String(text.value)
  92. },
  93. { immediate: true, flush: 'post' },
  94. )
  95. text.value = 'changed'
  96. expect(msg).toBe('start')
  97. return () => h('div', null, msg)
  98. })
  99. const app = createSSRApp(App)
  100. const ctx: SSRContext = {}
  101. const html = await renderToString(app, ctx)
  102. expect(ctx.__watcherHandles).toBeUndefined()
  103. expect(html).toMatch('start')
  104. await nextTick()
  105. expect(msg).toBe('start')
  106. })
  107. test('should not run non-immediate watchers registered after async context restore', async () => {
  108. const text = ref('start')
  109. let beforeAwaitTriggered = false
  110. let afterAwaitTriggered = false
  111. const App = defineComponent({
  112. async setup() {
  113. let __temp: any, __restore: any
  114. watch(text, () => {
  115. beforeAwaitTriggered = true
  116. })
  117. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  118. __temp = await __temp
  119. __restore()
  120. watch(text, () => {
  121. afterAwaitTriggered = true
  122. })
  123. text.value = 'changed'
  124. expect(beforeAwaitTriggered).toBe(false)
  125. expect(afterAwaitTriggered).toBe(false)
  126. return () => h('div', null, text.value)
  127. },
  128. })
  129. const app = createSSRApp(App)
  130. const ctx: SSRContext = {}
  131. const html = await renderToString(app, ctx)
  132. expect(ctx.__watcherHandles).toBeUndefined()
  133. expect(html).toMatch('changed')
  134. await nextTick()
  135. expect(beforeAwaitTriggered).toBe(false)
  136. expect(afterAwaitTriggered).toBe(false)
  137. })
  138. test('should not run non-immediate watchers registered after async context restore on rejection', async () => {
  139. const text = ref('start')
  140. let beforeAwaitTriggered = false
  141. let afterAwaitTriggered = false
  142. const App = defineComponent({
  143. async setup() {
  144. let __temp: any, __restore: any
  145. watch(text, () => {
  146. beforeAwaitTriggered = true
  147. })
  148. try {
  149. ;[__temp, __restore] = withAsyncContext(() =>
  150. Promise.reject(new Error('failed')),
  151. )
  152. __temp = await __temp
  153. __restore()
  154. } catch {}
  155. watch(text, () => {
  156. afterAwaitTriggered = true
  157. })
  158. text.value = 'changed'
  159. expect(beforeAwaitTriggered).toBe(false)
  160. expect(afterAwaitTriggered).toBe(false)
  161. return () => h('div', null, text.value)
  162. },
  163. })
  164. const app = createSSRApp(App)
  165. const ctx: SSRContext = {}
  166. const html = await renderToString(app, ctx)
  167. expect(ctx.__watcherHandles).toBeUndefined()
  168. expect(html).toMatch('changed')
  169. await nextTick()
  170. expect(beforeAwaitTriggered).toBe(false)
  171. expect(afterAwaitTriggered).toBe(false)
  172. })
  173. })
  174. describe('ssr: watchEffect', () => {
  175. test('should run with flush: sync', async () => {
  176. const text = ref('start')
  177. let msg = 'unchanged'
  178. const App = defineComponent(() => {
  179. watchEffect(
  180. () => {
  181. msg = text.value
  182. },
  183. { flush: 'sync' },
  184. )
  185. expect(msg).toBe('start')
  186. text.value = 'changed'
  187. expect(msg).toBe('changed')
  188. text.value = 'changed again'
  189. expect(msg).toBe('changed again')
  190. return () => h('div', null, msg)
  191. })
  192. const app = createSSRApp(App)
  193. const ctx: SSRContext = {}
  194. const html = await renderToString(app, ctx)
  195. expect(ctx.__watcherHandles!.length).toBe(0)
  196. expect(html).toMatch('changed again')
  197. await nextTick()
  198. expect(msg).toBe('changed again')
  199. })
  200. test('should run once with default flush (pre)', async () => {
  201. const text = ref('start')
  202. let msg = 'unchanged'
  203. const App = defineComponent(() => {
  204. watchEffect(() => {
  205. msg = text.value
  206. })
  207. text.value = 'changed'
  208. expect(msg).toBe('start')
  209. return () => h('div', null, msg)
  210. })
  211. const app = createSSRApp(App)
  212. const ctx: SSRContext = {}
  213. const html = await renderToString(app, ctx)
  214. expect(ctx.__watcherHandles).toBeUndefined()
  215. expect(html).toMatch('start')
  216. await nextTick()
  217. expect(msg).toBe('start')
  218. })
  219. test('should not run for flush: post', async () => {
  220. const text = ref('start')
  221. let msg = 'unchanged'
  222. const App = defineComponent(() => {
  223. watchEffect(
  224. () => {
  225. msg = text.value
  226. },
  227. { flush: 'post' },
  228. )
  229. text.value = 'changed'
  230. expect(msg).toBe('unchanged')
  231. return () => h('div', null, msg)
  232. })
  233. const app = createSSRApp(App)
  234. const ctx: SSRContext = {}
  235. const html = await renderToString(app, ctx)
  236. expect(ctx.__watcherHandles).toBeUndefined()
  237. expect(html).toMatch('unchanged')
  238. await nextTick()
  239. expect(msg).toBe('unchanged')
  240. })
  241. })