hmr.spec.ts 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485
  1. import {
  2. type HMRRuntime,
  3. computed,
  4. createApp,
  5. h,
  6. inject,
  7. nextTick,
  8. onActivated,
  9. onDeactivated,
  10. onMounted,
  11. onUnmounted,
  12. provide,
  13. ref,
  14. toDisplayString,
  15. } from '@vue/runtime-dom'
  16. import { compileToVaporRender as compileToFunction, makeRender } from './_utils'
  17. import {
  18. createComponent,
  19. createSlot,
  20. createTemplateRefSetter,
  21. createVaporApp,
  22. defineVaporAsyncComponent,
  23. defineVaporComponent,
  24. delegateEvents,
  25. renderEffect,
  26. setText,
  27. template,
  28. vaporInteropPlugin,
  29. withVaporCtx,
  30. } from '@vue/runtime-vapor'
  31. import { BindingTypes } from '@vue/compiler-core'
  32. import type { VaporComponent } from '../src/component'
  33. declare var __VUE_HMR_RUNTIME__: HMRRuntime
  34. const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
  35. const define = makeRender()
  36. const timeout = (n: number = 0) => new Promise(r => setTimeout(r, n))
  37. const triggerEvent = (type: string, el: Element) => {
  38. const event = new Event(type, { bubbles: true })
  39. el.dispatchEvent(event)
  40. }
  41. delegateEvents('click')
  42. beforeEach(() => {
  43. document.body.innerHTML = ''
  44. })
  45. describe('hot module replacement', () => {
  46. test('inject global runtime', () => {
  47. expect(createRecord).toBeDefined()
  48. expect(rerender).toBeDefined()
  49. expect(reload).toBeDefined()
  50. })
  51. test('createRecord', () => {
  52. expect(createRecord('test1', {})).toBe(true)
  53. // if id has already been created, should return false
  54. expect(createRecord('test1', {})).toBe(false)
  55. })
  56. test('rerender', async () => {
  57. const root = document.createElement('div')
  58. const parentId = 'test2-parent'
  59. const childId = 'test2-child'
  60. document.body.appendChild(root)
  61. const Child = defineVaporComponent({
  62. __hmrId: childId,
  63. render: compileToFunction('<div><slot/></div>'),
  64. })
  65. createRecord(childId, Child as any)
  66. const Parent = defineVaporComponent({
  67. __hmrId: parentId,
  68. components: { Child },
  69. setup() {
  70. const count = ref(0)
  71. return { count }
  72. },
  73. render: compileToFunction(
  74. `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`,
  75. ),
  76. })
  77. createRecord(parentId, Parent as any)
  78. const { mount } = define(Parent).create()
  79. mount(root)
  80. expect(root.innerHTML).toBe(`<div>0<div>0<!--slot--></div></div>`)
  81. // Perform some state change. This change should be preserved after the
  82. // re-render!
  83. // triggerEvent(root.children[0] as TestElement, 'click')
  84. triggerEvent('click', root.children[0])
  85. await nextTick()
  86. expect(root.innerHTML).toBe(`<div>1<div>1<!--slot--></div></div>`)
  87. // Update text while preserving state
  88. rerender(
  89. parentId,
  90. compileToFunction(
  91. `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`,
  92. ),
  93. )
  94. expect(root.innerHTML).toBe(`<div>1!<div>1<!--slot--></div></div>`)
  95. // Should force child update on slot content change
  96. rerender(
  97. parentId,
  98. compileToFunction(
  99. `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`,
  100. ),
  101. )
  102. expect(root.innerHTML).toBe(`<div>1!<div>1!<!--slot--></div></div>`)
  103. // Should force update element children despite block optimization
  104. rerender(
  105. parentId,
  106. compileToFunction(
  107. `<div @click="count++">{{ count }}<span>{{ count }}</span>
  108. <Child>{{ count }}!</Child>
  109. </div>`,
  110. ),
  111. )
  112. expect(root.innerHTML).toBe(
  113. `<div>1<span>1</span><div>1!<!--slot--></div></div>`,
  114. )
  115. // Should force update child slot elements
  116. rerender(
  117. parentId,
  118. compileToFunction(
  119. `<div @click="count++">
  120. <Child><span>{{ count }}</span></Child>
  121. </div>`,
  122. ),
  123. )
  124. expect(root.innerHTML).toBe(
  125. `<div><div><span>1</span><!--slot--></div></div>`,
  126. )
  127. })
  128. test('reload', async () => {
  129. const root = document.createElement('div')
  130. const childId = 'test3-child'
  131. const unmountSpy = vi.fn()
  132. const mountSpy = vi.fn()
  133. const Child = defineVaporComponent({
  134. __hmrId: childId,
  135. setup() {
  136. onUnmounted(unmountSpy)
  137. const count = ref(0)
  138. return { count }
  139. },
  140. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  141. })
  142. createRecord(childId, Child as any)
  143. const Parent = defineVaporComponent({
  144. __hmrId: 'parentId',
  145. render: () => createComponent(Child),
  146. })
  147. define(Parent).create().mount(root)
  148. expect(root.innerHTML).toBe(`<div>0</div>`)
  149. reload(childId, {
  150. __hmrId: childId,
  151. setup() {
  152. onMounted(mountSpy)
  153. const count = ref(1)
  154. return { count }
  155. },
  156. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  157. })
  158. await nextTick()
  159. expect(root.innerHTML).toBe(`<div>1</div>`)
  160. expect(unmountSpy).toHaveBeenCalledTimes(1)
  161. expect(mountSpy).toHaveBeenCalledTimes(1)
  162. })
  163. test('reload root vapor component should preserve appContext provide/inject', async () => {
  164. const root = document.createElement('div')
  165. const appId = 'test-root-reload-app-context'
  166. const Child = defineVaporComponent({
  167. setup() {
  168. const msg = inject('msg')
  169. return { msg }
  170. },
  171. render: compileToFunction(`<div>{{ msg }}</div>`),
  172. })
  173. const App = defineVaporComponent({
  174. __hmrId: appId,
  175. render: () => createComponent(Child),
  176. })
  177. createRecord(appId, App as any)
  178. const app = createVaporApp(App)
  179. app.provide('msg', 'app-injected')
  180. app.mount(root)
  181. expect(root.innerHTML).toBe(`<div>app-injected</div>`)
  182. reload(appId, {
  183. __vapor: true,
  184. __hmrId: appId,
  185. render: () => createComponent(Child),
  186. })
  187. await nextTick()
  188. expect(root.innerHTML).toBe(`<div>app-injected</div>`)
  189. })
  190. test('reload KeepAlive slot', async () => {
  191. const root = document.createElement('div')
  192. document.body.appendChild(root)
  193. const childId = 'test-child-keep-alive'
  194. const unmountSpy = vi.fn()
  195. const mountSpy = vi.fn()
  196. const activeSpy = vi.fn()
  197. const deactivatedSpy = vi.fn()
  198. const Child = defineVaporComponent({
  199. __hmrId: childId,
  200. setup() {
  201. onUnmounted(unmountSpy)
  202. const count = ref(0)
  203. return { count }
  204. },
  205. render: compileToFunction(`<div>{{ count }}</div>`),
  206. })
  207. createRecord(childId, Child as any)
  208. const Parent = defineVaporComponent({
  209. __hmrId: 'parentId',
  210. components: { Child },
  211. setup() {
  212. const toggle = ref(true)
  213. return { toggle }
  214. },
  215. render: compileToFunction(
  216. `<button @click="toggle = !toggle" />
  217. <KeepAlive><Child v-if="toggle" /></KeepAlive>`,
  218. ),
  219. })
  220. define(Parent).create().mount(root)
  221. expect(root.innerHTML).toBe(`<button></button><div>0</div><!--if-->`)
  222. reload(childId, {
  223. __hmrId: childId,
  224. __vapor: true,
  225. setup() {
  226. onMounted(mountSpy)
  227. onUnmounted(unmountSpy)
  228. onActivated(activeSpy)
  229. onDeactivated(deactivatedSpy)
  230. const count = ref(1)
  231. return { count }
  232. },
  233. render: compileToFunction(`<div>{{ count }}</div>`),
  234. })
  235. await nextTick()
  236. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  237. expect(unmountSpy).toHaveBeenCalledTimes(1)
  238. expect(mountSpy).toHaveBeenCalledTimes(1)
  239. expect(activeSpy).toHaveBeenCalledTimes(1)
  240. expect(deactivatedSpy).toHaveBeenCalledTimes(0)
  241. // should not unmount when toggling
  242. triggerEvent('click', root.children[0] as Element)
  243. await nextTick()
  244. expect(unmountSpy).toHaveBeenCalledTimes(1)
  245. expect(mountSpy).toHaveBeenCalledTimes(1)
  246. expect(activeSpy).toHaveBeenCalledTimes(1)
  247. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  248. // should not mount when toggling
  249. triggerEvent('click', root.children[0] as Element)
  250. await nextTick()
  251. expect(unmountSpy).toHaveBeenCalledTimes(1)
  252. expect(mountSpy).toHaveBeenCalledTimes(1)
  253. expect(activeSpy).toHaveBeenCalledTimes(2)
  254. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  255. })
  256. test('reload deactivated KeepAlive child', async () => {
  257. const root = document.createElement('div')
  258. document.body.appendChild(root)
  259. const childId = 'test-child-keep-alive-deactivated'
  260. const oldUnmountSpy = vi.fn()
  261. const oldActiveSpy = vi.fn()
  262. const oldDeactivatedSpy = vi.fn()
  263. const newUnmountSpy = vi.fn()
  264. const newMountSpy = vi.fn()
  265. const newActiveSpy = vi.fn()
  266. const newDeactivatedSpy = vi.fn()
  267. const Child = defineVaporComponent({
  268. __hmrId: childId,
  269. setup() {
  270. onUnmounted(oldUnmountSpy)
  271. onActivated(oldActiveSpy)
  272. onDeactivated(oldDeactivatedSpy)
  273. const count = ref(0)
  274. return { count }
  275. },
  276. render: compileToFunction(`<div>{{ count }}</div>`),
  277. })
  278. createRecord(childId, Child as any)
  279. const Parent = defineVaporComponent({
  280. __hmrId: 'parentId-keep-alive-deactivated',
  281. components: { Child },
  282. setup() {
  283. const toggle = ref(true)
  284. return { toggle }
  285. },
  286. render: compileToFunction(
  287. `<button @click="toggle = !toggle" />
  288. <KeepAlive><Child v-if="toggle" /></KeepAlive>`,
  289. ),
  290. })
  291. define(Parent).create().mount(root)
  292. expect(root.innerHTML).toBe(`<button></button><div>0</div><!--if-->`)
  293. expect(oldActiveSpy).toHaveBeenCalledTimes(1)
  294. expect(oldDeactivatedSpy).toHaveBeenCalledTimes(0)
  295. expect(oldUnmountSpy).toHaveBeenCalledTimes(0)
  296. // deactivate and move child into KeepAlive cache
  297. triggerEvent('click', root.children[0] as Element)
  298. await nextTick()
  299. expect(root.innerHTML).toBe(`<button></button><!--if-->`)
  300. expect(oldDeactivatedSpy).toHaveBeenCalledTimes(1)
  301. expect(oldUnmountSpy).toHaveBeenCalledTimes(0)
  302. // reload while child is cached but inactive
  303. reload(childId, {
  304. __hmrId: childId,
  305. __vapor: true,
  306. setup() {
  307. onMounted(newMountSpy)
  308. onUnmounted(newUnmountSpy)
  309. onActivated(newActiveSpy)
  310. onDeactivated(newDeactivatedSpy)
  311. const count = ref(1)
  312. return { count }
  313. },
  314. render: compileToFunction(`<div>{{ count }}</div>`),
  315. })
  316. await nextTick()
  317. expect(root.innerHTML).toBe(`<button></button><!--if-->`)
  318. // old cached instance should be unmounted during KeepAlive HMR rerender
  319. expect(oldUnmountSpy).toHaveBeenCalledTimes(1)
  320. // re-activate should render the new component instance
  321. triggerEvent('click', root.children[0] as Element)
  322. await nextTick()
  323. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  324. expect(newMountSpy).toHaveBeenCalledTimes(1)
  325. expect(newActiveSpy).toHaveBeenCalledTimes(1)
  326. expect(newDeactivatedSpy).toHaveBeenCalledTimes(0)
  327. // subsequent toggles should use KeepAlive cache for the new instance
  328. triggerEvent('click', root.children[0] as Element)
  329. await nextTick()
  330. expect(root.innerHTML).toBe(`<button></button><!--if-->`)
  331. expect(newMountSpy).toHaveBeenCalledTimes(1)
  332. expect(newActiveSpy).toHaveBeenCalledTimes(1)
  333. expect(newDeactivatedSpy).toHaveBeenCalledTimes(1)
  334. triggerEvent('click', root.children[0] as Element)
  335. await nextTick()
  336. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  337. expect(newMountSpy).toHaveBeenCalledTimes(1)
  338. expect(newActiveSpy).toHaveBeenCalledTimes(2)
  339. expect(newDeactivatedSpy).toHaveBeenCalledTimes(1)
  340. expect(newUnmountSpy).toHaveBeenCalledTimes(0)
  341. })
  342. test('reload KeepAlive slot in Transition', async () => {
  343. const root = document.createElement('div')
  344. document.body.appendChild(root)
  345. const childId = 'test-transition-keep-alive-reload'
  346. const unmountSpy = vi.fn()
  347. const mountSpy = vi.fn()
  348. const activeSpy = vi.fn()
  349. const deactivatedSpy = vi.fn()
  350. const Child = defineVaporComponent({
  351. __hmrId: childId,
  352. setup() {
  353. onUnmounted(unmountSpy)
  354. const count = ref(0)
  355. return { count }
  356. },
  357. render: compileToFunction(`<div>{{ count }}</div>`),
  358. })
  359. createRecord(childId, Child as any)
  360. const Parent = defineVaporComponent({
  361. __hmrId: 'parentId',
  362. components: { Child },
  363. setup() {
  364. const toggle = ref(true)
  365. function onLeave(_: any, done: Function) {
  366. setTimeout(done, 0)
  367. }
  368. return { toggle, onLeave }
  369. },
  370. render: compileToFunction(
  371. `<button @click="toggle = !toggle" />
  372. <Transition @leave="onLeave">
  373. <KeepAlive><Child v-if="toggle" /></KeepAlive>
  374. </Transition>`,
  375. ),
  376. })
  377. define(Parent).create().mount(root)
  378. expect(root.innerHTML).toBe(`<button></button><div>0</div><!--if-->`)
  379. reload(childId, {
  380. __hmrId: childId,
  381. __vapor: true,
  382. setup() {
  383. onMounted(mountSpy)
  384. onUnmounted(unmountSpy)
  385. onActivated(activeSpy)
  386. onDeactivated(deactivatedSpy)
  387. const count = ref(1)
  388. return { count }
  389. },
  390. render: compileToFunction(`<div>{{ count }}</div>`),
  391. })
  392. await nextTick()
  393. await new Promise(r => setTimeout(r, 0))
  394. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  395. expect(unmountSpy).toHaveBeenCalledTimes(1)
  396. expect(mountSpy).toHaveBeenCalledTimes(1)
  397. expect(activeSpy).toHaveBeenCalledTimes(1)
  398. expect(deactivatedSpy).toHaveBeenCalledTimes(0)
  399. // should not unmount when toggling
  400. triggerEvent('click', root.children[0] as Element)
  401. await nextTick()
  402. expect(root.innerHTML).toBe(`<button></button><!--if-->`)
  403. expect(unmountSpy).toHaveBeenCalledTimes(1)
  404. expect(mountSpy).toHaveBeenCalledTimes(1)
  405. expect(activeSpy).toHaveBeenCalledTimes(1)
  406. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  407. // should not mount when toggling
  408. triggerEvent('click', root.children[0] as Element)
  409. await nextTick()
  410. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  411. expect(unmountSpy).toHaveBeenCalledTimes(1)
  412. expect(mountSpy).toHaveBeenCalledTimes(1)
  413. expect(activeSpy).toHaveBeenCalledTimes(2)
  414. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  415. })
  416. test('reload KeepAlive slot in Transition with out-in', async () => {
  417. const root = document.createElement('div')
  418. document.body.appendChild(root)
  419. const childId = 'test-transition-keep-alive-reload-with-out-in'
  420. const unmountSpy = vi.fn()
  421. const mountSpy = vi.fn()
  422. const activeSpy = vi.fn()
  423. const deactivatedSpy = vi.fn()
  424. const Child = defineVaporComponent({
  425. name: 'original',
  426. __hmrId: childId,
  427. setup() {
  428. onUnmounted(unmountSpy)
  429. const count = ref(0)
  430. return { count }
  431. },
  432. render: compileToFunction(`<div>{{ count }}</div>`),
  433. })
  434. createRecord(childId, Child as any)
  435. const Parent = defineVaporComponent({
  436. components: { Child },
  437. setup() {
  438. function onLeave(_: any, done: Function) {
  439. setTimeout(done, 0)
  440. }
  441. const toggle = ref(true)
  442. return { toggle, onLeave }
  443. },
  444. render: compileToFunction(
  445. `<button @click="toggle = !toggle" />
  446. <Transition mode="out-in" @leave="onLeave">
  447. <KeepAlive><Child v-if="toggle" /></KeepAlive>
  448. </Transition>`,
  449. ),
  450. })
  451. define(Parent).create().mount(root)
  452. expect(root.innerHTML).toBe(`<button></button><div>0</div><!--if-->`)
  453. reload(childId, {
  454. name: 'updated',
  455. __hmrId: childId,
  456. __vapor: true,
  457. setup() {
  458. onMounted(mountSpy)
  459. onUnmounted(unmountSpy)
  460. onActivated(activeSpy)
  461. onDeactivated(deactivatedSpy)
  462. const count = ref(1)
  463. return { count }
  464. },
  465. render: compileToFunction(`<div>{{ count }}</div>`),
  466. })
  467. await nextTick()
  468. await new Promise(r => setTimeout(r, 0))
  469. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  470. expect(unmountSpy).toHaveBeenCalledTimes(1)
  471. expect(mountSpy).toHaveBeenCalledTimes(1)
  472. expect(activeSpy).toHaveBeenCalledTimes(1)
  473. expect(deactivatedSpy).toHaveBeenCalledTimes(0)
  474. // should not unmount when toggling
  475. triggerEvent('click', root.children[0] as Element)
  476. await nextTick()
  477. await new Promise(r => setTimeout(r, 0))
  478. expect(root.innerHTML).toBe(`<button></button><!--if-->`)
  479. expect(unmountSpy).toHaveBeenCalledTimes(1)
  480. expect(mountSpy).toHaveBeenCalledTimes(1)
  481. expect(activeSpy).toHaveBeenCalledTimes(1)
  482. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  483. // should not mount when toggling
  484. triggerEvent('click', root.children[0] as Element)
  485. await nextTick()
  486. expect(root.innerHTML).toBe(`<button></button><div>1</div><!--if-->`)
  487. expect(unmountSpy).toHaveBeenCalledTimes(1)
  488. expect(mountSpy).toHaveBeenCalledTimes(1)
  489. expect(activeSpy).toHaveBeenCalledTimes(2)
  490. expect(deactivatedSpy).toHaveBeenCalledTimes(1)
  491. })
  492. // TODO: renderEffect not re-run after child reload
  493. // it requires parent rerender to align with vdom
  494. test.todo('reload: avoid infinite recursion', async () => {
  495. const root = document.createElement('div')
  496. document.body.appendChild(root)
  497. const childId = 'test-child-6930'
  498. const unmountSpy = vi.fn()
  499. const mountSpy = vi.fn()
  500. const Child = defineVaporComponent({
  501. __hmrId: childId,
  502. setup(_, { expose }) {
  503. const count = ref(0)
  504. expose({
  505. count,
  506. })
  507. onUnmounted(unmountSpy)
  508. return { count }
  509. },
  510. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  511. })
  512. createRecord(childId, Child as any)
  513. const Parent = defineVaporComponent({
  514. setup() {
  515. const com1 = ref()
  516. const changeRef1 = (value: any) => (com1.value = value)
  517. const com2 = ref()
  518. const changeRef2 = (value: any) => (com2.value = value)
  519. const setRef = createTemplateRefSetter()
  520. const n0 = createComponent(Child)
  521. setRef(n0, changeRef1)
  522. const n1 = createComponent(Child)
  523. setRef(n1, changeRef2)
  524. const n2 = template(' ')() as any
  525. renderEffect(() => {
  526. setText(n2, toDisplayString(com1.value.count))
  527. })
  528. return [n0, n1, n2]
  529. },
  530. })
  531. define(Parent).create().mount(root)
  532. await nextTick()
  533. expect(root.innerHTML).toBe(`<div>0</div><div>0</div>0`)
  534. reload(childId, {
  535. __hmrId: childId,
  536. __vapor: true,
  537. setup() {
  538. onMounted(mountSpy)
  539. const count = ref(1)
  540. return { count }
  541. },
  542. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  543. })
  544. await nextTick()
  545. expect(root.innerHTML).toBe(`<div>1</div><div>1</div>1`)
  546. expect(unmountSpy).toHaveBeenCalledTimes(2)
  547. expect(mountSpy).toHaveBeenCalledTimes(2)
  548. })
  549. test('static el reference', async () => {
  550. const root = document.createElement('div')
  551. document.body.appendChild(root)
  552. const id = 'test-static-el'
  553. const template = `<div>
  554. <div>{{ count }}</div>
  555. <button @click="count++">++</button>
  556. </div>`
  557. const Comp = defineVaporComponent({
  558. __hmrId: id,
  559. setup() {
  560. const count = ref(0)
  561. return { count }
  562. },
  563. render: compileToFunction(template),
  564. })
  565. createRecord(id, Comp as any)
  566. define(Comp).create().mount(root)
  567. expect(root.innerHTML).toBe(`<div><div>0</div><button>++</button></div>`)
  568. // 1. click to trigger update
  569. triggerEvent('click', root.children[0].children[1] as Element)
  570. await nextTick()
  571. expect(root.innerHTML).toBe(`<div><div>1</div><button>++</button></div>`)
  572. // 2. trigger HMR
  573. rerender(
  574. id,
  575. compileToFunction(template.replace(`<button`, `<button class="foo"`)),
  576. )
  577. expect(root.innerHTML).toBe(
  578. `<div><div>1</div><button class="foo">++</button></div>`,
  579. )
  580. })
  581. test('force update child component w/ static props', () => {
  582. const root = document.createElement('div')
  583. const parentId = 'test-force-props-parent'
  584. const childId = 'test-force-props-child'
  585. const Child = defineVaporComponent({
  586. __hmrId: childId,
  587. props: {
  588. msg: String,
  589. },
  590. render: compileToFunction(`<div>{{ msg }}</div>`, {
  591. bindingMetadata: {
  592. msg: BindingTypes.PROPS,
  593. },
  594. }),
  595. })
  596. createRecord(childId, Child as any)
  597. const Parent = defineVaporComponent({
  598. __hmrId: parentId,
  599. components: { Child },
  600. render: compileToFunction(`<Child msg="foo" />`),
  601. })
  602. createRecord(parentId, Parent as any)
  603. define(Parent).create().mount(root)
  604. expect(root.innerHTML).toBe(`<div>foo</div>`)
  605. rerender(parentId, compileToFunction(`<Child msg="bar" />`))
  606. expect(root.innerHTML).toBe(`<div>bar</div>`)
  607. })
  608. test('remove static class from parent', () => {
  609. const root = document.createElement('div')
  610. const parentId = 'test-force-class-parent'
  611. const childId = 'test-force-class-child'
  612. const Child = defineVaporComponent({
  613. __hmrId: childId,
  614. render: compileToFunction(`<div>child</div>`),
  615. })
  616. createRecord(childId, Child as any)
  617. const Parent = defineVaporComponent({
  618. __hmrId: parentId,
  619. components: { Child },
  620. render: compileToFunction(`<Child class="test" />`),
  621. })
  622. createRecord(parentId, Parent as any)
  623. define(Parent).create().mount(root)
  624. expect(root.innerHTML).toBe(`<div class="test">child</div>`)
  625. rerender(parentId, compileToFunction(`<Child/>`))
  626. expect(root.innerHTML).toBe(`<div>child</div>`)
  627. })
  628. test('rerender if any parent in the parent chain', () => {
  629. const root = document.createElement('div')
  630. const parent = 'test-force-props-parent-'
  631. const childId = 'test-force-props-child'
  632. const numberOfParents = 5
  633. const Child = defineVaporComponent({
  634. __hmrId: childId,
  635. render: compileToFunction(`<div>child</div>`),
  636. })
  637. createRecord(childId, Child as any)
  638. const components: VaporComponent[] = []
  639. for (let i = 0; i < numberOfParents; i++) {
  640. const parentId = `${parent}${i}`
  641. const parentComp: VaporComponent = {
  642. __vapor: true,
  643. __hmrId: parentId,
  644. }
  645. components.push(parentComp)
  646. if (i === 0) {
  647. parentComp.render = compileToFunction(`<Child />`)
  648. parentComp.components = {
  649. Child,
  650. }
  651. } else {
  652. parentComp.render = compileToFunction(`<Parent />`)
  653. parentComp.components = {
  654. Parent: components[i - 1],
  655. }
  656. }
  657. createRecord(parentId, parentComp as any)
  658. }
  659. const last = components[components.length - 1]
  660. define(last).create().mount(root)
  661. expect(root.innerHTML).toBe(`<div>child</div>`)
  662. rerender(last.__hmrId!, compileToFunction(`<Parent class="test"/>`))
  663. expect(root.innerHTML).toBe(`<div class="test">child</div>`)
  664. })
  665. test('rerender with Teleport', () => {
  666. const root = document.createElement('div')
  667. const target = document.createElement('div')
  668. document.body.appendChild(root)
  669. document.body.appendChild(target)
  670. const parentId = 'parent-teleport'
  671. const Child = defineVaporComponent({
  672. setup() {
  673. return { target }
  674. },
  675. render: compileToFunction(`
  676. <teleport :to="target">
  677. <div>
  678. <slot/>
  679. </div>
  680. </teleport>
  681. `),
  682. })
  683. const Parent = {
  684. __vapor: true,
  685. __hmrId: parentId,
  686. components: { Child },
  687. render: compileToFunction(`
  688. <Child>
  689. <template #default>
  690. <div>1</div>
  691. </template>
  692. </Child>
  693. `),
  694. }
  695. createRecord(parentId, Parent as any)
  696. define(Parent).create().mount(root)
  697. expect(root.innerHTML).toBe(`<!--teleport start--><!--teleport end-->`)
  698. expect(target.innerHTML).toBe(`<div><div>1</div><!--slot--></div>`)
  699. rerender(
  700. parentId,
  701. compileToFunction(`
  702. <Child>
  703. <template #default>
  704. <div>1</div>
  705. <div>2</div>
  706. </template>
  707. </Child>
  708. `),
  709. )
  710. expect(root.innerHTML).toBe(`<!--teleport start--><!--teleport end-->`)
  711. expect(target.innerHTML).toBe(
  712. `<div><div>1</div><div>2</div><!--slot--></div>`,
  713. )
  714. })
  715. test('rerender for component that has no active instance yet', () => {
  716. const id = 'no-active-instance-rerender'
  717. const Foo = {
  718. __vapor: true,
  719. __hmrId: id,
  720. render: () => template('foo')(),
  721. }
  722. createRecord(id, Foo)
  723. rerender(id, () => template('bar')())
  724. const root = document.createElement('div')
  725. define(Foo).create().mount(root)
  726. expect(root.innerHTML).toBe('bar')
  727. })
  728. test('reload for component that has no active instance yet', () => {
  729. const id = 'no-active-instance-reload'
  730. const Foo = {
  731. __vapor: true,
  732. __hmrId: id,
  733. render: () => template('foo')(),
  734. }
  735. createRecord(id, Foo)
  736. reload(id, {
  737. __hmrId: id,
  738. render: () => template('bar')(),
  739. })
  740. const root = document.createElement('div')
  741. define(Foo).render({}, root)
  742. expect(root.innerHTML).toBe('bar')
  743. })
  744. test('force update slot content change', () => {
  745. const root = document.createElement('div')
  746. const parentId = 'test-force-computed-parent'
  747. const childId = 'test-force-computed-child'
  748. const Child = {
  749. __vapor: true,
  750. __hmrId: childId,
  751. setup(_: any, { slots }: any) {
  752. const slotContent = computed(() => {
  753. return slots.default?.()
  754. })
  755. return { slotContent }
  756. },
  757. render: compileToFunction(`<component :is="() => slotContent" />`),
  758. }
  759. createRecord(childId, Child)
  760. const Parent = {
  761. __vapor: true,
  762. __hmrId: parentId,
  763. components: { Child },
  764. render: compileToFunction(`<Child>1</Child>`),
  765. }
  766. createRecord(parentId, Parent)
  767. // render(h(Parent), root)
  768. define(Parent).render({}, root)
  769. expect(root.innerHTML).toBe(`1<!--dynamic-component-->`)
  770. rerender(parentId, compileToFunction(`<Child>2</Child>`))
  771. expect(root.innerHTML).toBe(`2<!--dynamic-component-->`)
  772. })
  773. // #11248
  774. test('reload async component with multiple instances', async () => {
  775. const root = document.createElement('div')
  776. const childId = 'test-child-id'
  777. const Child = {
  778. __vapor: true,
  779. __hmrId: childId,
  780. setup() {
  781. const count = ref(0)
  782. return { count }
  783. },
  784. render: compileToFunction(`<div>{{ count }}</div>`),
  785. }
  786. const Comp = defineVaporAsyncComponent(() => Promise.resolve(Child))
  787. const appId = 'test-app-id'
  788. const App = {
  789. __hmrId: appId,
  790. render() {
  791. return [createComponent(Comp), createComponent(Comp)]
  792. },
  793. }
  794. createRecord(appId, App)
  795. define(App).render({}, root)
  796. await timeout()
  797. expect(root.innerHTML).toBe(
  798. `<div>0</div><!--async component--><div>0</div><!--async component-->`,
  799. )
  800. // change count to 1
  801. reload(childId, {
  802. __vapor: true,
  803. __hmrId: childId,
  804. setup() {
  805. const count = ref(1)
  806. return { count }
  807. },
  808. render: compileToFunction(`<div>{{ count }}</div>`),
  809. })
  810. await timeout()
  811. expect(root.innerHTML).toBe(
  812. `<div>1</div><!--async component--><div>1</div><!--async component-->`,
  813. )
  814. })
  815. test.todo('reload async child wrapped in Suspense + KeepAlive', async () => {
  816. // const id = 'async-child-reload'
  817. // const AsyncChild: ComponentOptions = {
  818. // __hmrId: id,
  819. // async setup() {
  820. // await nextTick()
  821. // return () => 'foo'
  822. // },
  823. // }
  824. // createRecord(id, AsyncChild)
  825. // const appId = 'test-app-id'
  826. // const App: ComponentOptions = {
  827. // __hmrId: appId,
  828. // components: { AsyncChild },
  829. // render: compileToFunction(`
  830. // <div>
  831. // <Suspense>
  832. // <KeepAlive>
  833. // <AsyncChild />
  834. // </KeepAlive>
  835. // </Suspense>
  836. // </div>
  837. // `),
  838. // }
  839. // const root = nodeOps.createElement('div')
  840. // render(h(App), root)
  841. // expect(serializeInner(root)).toBe('<div><!----></div>')
  842. // await timeout()
  843. // expect(serializeInner(root)).toBe('<div>foo</div>')
  844. // reload(id, {
  845. // __hmrId: id,
  846. // async setup() {
  847. // await nextTick()
  848. // return () => 'bar'
  849. // },
  850. // })
  851. // await timeout()
  852. // expect(serializeInner(root)).toBe('<div>bar</div>')
  853. })
  854. test.todo('multi reload child wrapped in Suspense + KeepAlive', async () => {
  855. // const id = 'test-child-reload-3'
  856. // const Child: ComponentOptions = {
  857. // __hmrId: id,
  858. // setup() {
  859. // const count = ref(0)
  860. // return { count }
  861. // },
  862. // render: compileToFunction(`<div>{{ count }}</div>`),
  863. // }
  864. // createRecord(id, Child)
  865. // const appId = 'test-app-id'
  866. // const App: ComponentOptions = {
  867. // __hmrId: appId,
  868. // components: { Child },
  869. // render: compileToFunction(`
  870. // <KeepAlive>
  871. // <Suspense>
  872. // <Child />
  873. // </Suspense>
  874. // </KeepAlive>
  875. // `),
  876. // }
  877. // const root = nodeOps.createElement('div')
  878. // render(h(App), root)
  879. // expect(serializeInner(root)).toBe('<div>0</div>')
  880. // await timeout()
  881. // reload(id, {
  882. // __hmrId: id,
  883. // setup() {
  884. // const count = ref(1)
  885. // return { count }
  886. // },
  887. // render: compileToFunction(`<div>{{ count }}</div>`),
  888. // })
  889. // await timeout()
  890. // expect(serializeInner(root)).toBe('<div>1</div>')
  891. // reload(id, {
  892. // __hmrId: id,
  893. // setup() {
  894. // const count = ref(2)
  895. // return { count }
  896. // },
  897. // render: compileToFunction(`<div>{{ count }}</div>`),
  898. // })
  899. // await timeout()
  900. // expect(serializeInner(root)).toBe('<div>2</div>')
  901. })
  902. test('rerender for nested component', () => {
  903. const id = 'child-nested-rerender'
  904. const Foo = {
  905. __vapor: true,
  906. __hmrId: id,
  907. setup(_ctx: any, { slots }: any) {
  908. return slots.default()
  909. },
  910. }
  911. createRecord(id, Foo)
  912. const parentId = 'parent-nested-rerender'
  913. const Parent = {
  914. __vapor: true,
  915. __hmrId: parentId,
  916. render() {
  917. return createComponent(
  918. Foo,
  919. {},
  920. {
  921. default: withVaporCtx(() => {
  922. return createSlot('default')
  923. }),
  924. },
  925. )
  926. },
  927. }
  928. const appId = 'app-nested-rerender'
  929. const App = {
  930. __vapor: true,
  931. __hmrId: appId,
  932. render: () =>
  933. createComponent(
  934. Parent,
  935. {},
  936. {
  937. default: withVaporCtx(() => {
  938. return createComponent(
  939. Foo,
  940. {},
  941. {
  942. default: () => template('foo')(),
  943. },
  944. )
  945. }),
  946. },
  947. ),
  948. }
  949. createRecord(parentId, App)
  950. const root = document.createElement('div')
  951. define(App).render({}, root)
  952. expect(root.innerHTML).toBe('foo<!--slot-->')
  953. rerender(id, () => template('bar')())
  954. expect(root.innerHTML).toBe('bar')
  955. })
  956. test('reload nested components from single update', async () => {
  957. const innerId = 'nested-reload-inner'
  958. const outerId = 'nested-reload-outer'
  959. let Inner = {
  960. __vapor: true,
  961. __hmrId: innerId,
  962. render() {
  963. return template('<div>foo</div>')()
  964. },
  965. }
  966. let Outer = {
  967. __vapor: true,
  968. __hmrId: outerId,
  969. render() {
  970. return createComponent(Inner as any)
  971. },
  972. }
  973. createRecord(innerId, Inner)
  974. createRecord(outerId, Outer)
  975. const App = {
  976. __vapor: true,
  977. render: () => createComponent(Outer),
  978. }
  979. const root = document.createElement('div')
  980. define(App).render({}, root)
  981. expect(root.innerHTML).toBe('<div>foo</div>')
  982. Inner = {
  983. __vapor: true,
  984. __hmrId: innerId,
  985. render() {
  986. return template('<div>bar</div>')()
  987. },
  988. }
  989. Outer = {
  990. __vapor: true,
  991. __hmrId: outerId,
  992. render() {
  993. return createComponent(Inner as any)
  994. },
  995. }
  996. // trigger reload for both Outer and Inner
  997. reload(outerId, Outer)
  998. reload(innerId, Inner)
  999. await nextTick()
  1000. expect(root.innerHTML).toBe('<div>bar</div>')
  1001. })
  1002. test('child reload + parent reload', async () => {
  1003. const root = document.createElement('div')
  1004. const childId = 'test1-child-reload'
  1005. const parentId = 'test1-parent-reload'
  1006. const { component: Child } = define({
  1007. __hmrId: childId,
  1008. setup() {
  1009. const msg = ref('child')
  1010. return { msg }
  1011. },
  1012. render: compileToFunction(`<div>{{ msg }}</div>`),
  1013. })
  1014. createRecord(childId, Child as any)
  1015. const { mount, component: Parent } = define({
  1016. __hmrId: parentId,
  1017. components: { Child },
  1018. setup() {
  1019. const msg = ref('root')
  1020. return { msg }
  1021. },
  1022. render: compileToFunction(`<Child/><div>{{ msg }}</div>`),
  1023. }).create()
  1024. createRecord(parentId, Parent as any)
  1025. mount(root)
  1026. expect(root.innerHTML).toMatchInlineSnapshot(
  1027. `"<div>child</div><div>root</div>"`,
  1028. )
  1029. // reload child
  1030. reload(childId, {
  1031. __hmrId: childId,
  1032. __vapor: true,
  1033. setup() {
  1034. const msg = ref('child changed')
  1035. return { msg }
  1036. },
  1037. render: compileToFunction(`<div>{{ msg }}</div>`),
  1038. })
  1039. expect(root.innerHTML).toMatchInlineSnapshot(
  1040. `"<div>child changed</div><div>root</div>"`,
  1041. )
  1042. // reload child again
  1043. reload(childId, {
  1044. __hmrId: childId,
  1045. __vapor: true,
  1046. setup() {
  1047. const msg = ref('child changed2')
  1048. return { msg }
  1049. },
  1050. render: compileToFunction(`<div>{{ msg }}</div>`),
  1051. })
  1052. expect(root.innerHTML).toMatchInlineSnapshot(
  1053. `"<div>child changed2</div><div>root</div>"`,
  1054. )
  1055. // reload parent
  1056. reload(parentId, {
  1057. __hmrId: parentId,
  1058. __vapor: true,
  1059. // @ts-expect-error
  1060. components: { Child },
  1061. setup() {
  1062. const msg = ref('root changed')
  1063. return { msg }
  1064. },
  1065. render: compileToFunction(`<Child/><div>{{ msg }}</div>`),
  1066. })
  1067. expect(root.innerHTML).toMatchInlineSnapshot(
  1068. `"<div>child changed2</div><div>root changed</div>"`,
  1069. )
  1070. })
  1071. test('child reload in dynamic branch should not break subsequent parent reload', async () => {
  1072. const root = document.createElement('div')
  1073. const childId = 'test-dynamic-child-reload'
  1074. const parentId = 'test-dynamic-parent-reload'
  1075. const Child = defineVaporComponent({
  1076. __hmrId: childId,
  1077. setup() {
  1078. const msg = ref('child')
  1079. return { msg }
  1080. },
  1081. render: compileToFunction(`<div>{{ msg }}</div>`),
  1082. })
  1083. createRecord(childId, Child as any)
  1084. const { mount, component: Parent } = define({
  1085. __hmrId: parentId,
  1086. components: { Child },
  1087. setup() {
  1088. const ok = ref(true)
  1089. return { ok }
  1090. },
  1091. render: compileToFunction(`<Child v-if="ok" />`),
  1092. }).create()
  1093. createRecord(parentId, Parent as any)
  1094. mount(root)
  1095. expect(root.innerHTML).toBe(`<div>child</div><!--if-->`)
  1096. reload(childId, {
  1097. __vapor: true,
  1098. __hmrId: childId,
  1099. setup() {
  1100. const msg = ref('child changed')
  1101. return { msg }
  1102. },
  1103. render: compileToFunction(`<div>{{ msg }}</div>`),
  1104. })
  1105. expect(root.innerHTML).toBe(`<div>child changed</div><!--if-->`)
  1106. reload(parentId, {
  1107. __vapor: true,
  1108. __hmrId: parentId,
  1109. components: { Child },
  1110. setup() {
  1111. const ok = ref(true)
  1112. return { ok }
  1113. },
  1114. render: compileToFunction(`<Child v-if="ok" />`),
  1115. })
  1116. await nextTick()
  1117. expect(root.innerHTML).toBe(`<div>child changed</div><!--if-->`)
  1118. })
  1119. test('child reload with multiple instances in dynamic branch should keep parent reload stable', async () => {
  1120. const root = document.createElement('div')
  1121. const childId = 'test-dynamic-multi-child-reload'
  1122. const parentId = 'test-dynamic-multi-parent-reload'
  1123. const Child = defineVaporComponent({
  1124. __hmrId: childId,
  1125. setup() {
  1126. const msg = ref('child')
  1127. return { msg }
  1128. },
  1129. render: compileToFunction(`<div>{{ msg }}</div>`),
  1130. })
  1131. createRecord(childId, Child as any)
  1132. const { mount, component: Parent } = define({
  1133. __hmrId: parentId,
  1134. components: { Child },
  1135. setup() {
  1136. const ok = ref(true)
  1137. return { ok }
  1138. },
  1139. render: compileToFunction(
  1140. `<template v-if="ok"><Child/><Child/></template>`,
  1141. ),
  1142. }).create()
  1143. createRecord(parentId, Parent as any)
  1144. mount(root)
  1145. expect(root.textContent).toBe(`childchild`)
  1146. reload(childId, {
  1147. __vapor: true,
  1148. __hmrId: childId,
  1149. setup() {
  1150. const msg = ref('child changed')
  1151. return { msg }
  1152. },
  1153. render: compileToFunction(`<div>{{ msg }}</div>`),
  1154. })
  1155. expect(root.textContent).toBe(`child changedchild changed`)
  1156. reload(parentId, {
  1157. __vapor: true,
  1158. __hmrId: parentId,
  1159. components: { Child },
  1160. setup() {
  1161. const ok = ref(true)
  1162. return { ok }
  1163. },
  1164. render: compileToFunction(
  1165. `<template v-if="ok"><Child/><Child/></template>`,
  1166. ),
  1167. })
  1168. await nextTick()
  1169. expect(root.textContent).toBe(`child changedchild changed`)
  1170. })
  1171. test('child reload in teleport dynamic branch should not break subsequent parent reload', async () => {
  1172. const root = document.createElement('div')
  1173. const target = document.createElement('div')
  1174. document.body.appendChild(root)
  1175. document.body.appendChild(target)
  1176. const childId = 'test-teleport-dynamic-child-reload'
  1177. const parentId = 'test-teleport-dynamic-parent-reload'
  1178. const Child = defineVaporComponent({
  1179. __hmrId: childId,
  1180. setup() {
  1181. const msg = ref('child')
  1182. return { msg }
  1183. },
  1184. render: compileToFunction(`<div>{{ msg }}</div>`),
  1185. })
  1186. createRecord(childId, Child as any)
  1187. const { mount, component: Parent } = define({
  1188. __hmrId: parentId,
  1189. components: { Child },
  1190. setup() {
  1191. const ok = ref(true)
  1192. return { ok, target }
  1193. },
  1194. render: compileToFunction(
  1195. `<teleport :to="target"><template v-if="ok"><Child/><span>sibling</span></template></teleport>`,
  1196. ),
  1197. }).create()
  1198. createRecord(parentId, Parent as any)
  1199. mount(root)
  1200. expect(target.textContent).toBe(`childsibling`)
  1201. reload(childId, {
  1202. __vapor: true,
  1203. __hmrId: childId,
  1204. setup() {
  1205. const msg = ref('child changed')
  1206. return { msg }
  1207. },
  1208. render: compileToFunction(`<div>{{ msg }}</div>`),
  1209. })
  1210. expect(target.textContent).toBe(`child changedsibling`)
  1211. reload(parentId, {
  1212. __vapor: true,
  1213. __hmrId: parentId,
  1214. components: { Child },
  1215. setup() {
  1216. const ok = ref(true)
  1217. return { ok, target }
  1218. },
  1219. render: compileToFunction(
  1220. `<teleport :to="target"><template v-if="ok"><Child/><span>sibling</span></template></teleport>`,
  1221. ),
  1222. })
  1223. await nextTick()
  1224. expect(target.textContent).toBe(`child changedsibling`)
  1225. })
  1226. // Vapor router-view has no render function (setup-only).
  1227. // When HMR rerender is triggered, the setup function is re-executed.
  1228. // Ensure provide() warning is suppressed.
  1229. test('rerender setup-only component', async () => {
  1230. const childId = 'test-child-reload-01'
  1231. const Child = defineVaporComponent({
  1232. __hmrId: childId,
  1233. render: compileToFunction(`<div>foo</div>`),
  1234. })
  1235. createRecord(childId, Child as any)
  1236. // without a render function
  1237. const Parent = defineVaporComponent({
  1238. setup() {
  1239. provide('foo', 'bar')
  1240. return createComponent(Child)
  1241. },
  1242. })
  1243. const { html } = define({
  1244. setup() {
  1245. return createComponent(Parent)
  1246. },
  1247. }).render()
  1248. expect(html()).toBe('<div>foo</div>')
  1249. // will trigger parent rerender
  1250. reload(childId, {
  1251. __hmrId: childId,
  1252. render: compileToFunction(`<div>bar</div>`),
  1253. })
  1254. await nextTick()
  1255. expect(html()).toBe('<div>bar</div>')
  1256. expect('provide() can only be used inside setup()').not.toHaveBeenWarned()
  1257. })
  1258. describe('switch vapor/vdom modes', () => {
  1259. test('vapor -> vdom', async () => {
  1260. const id = 'vapor-to-vdom'
  1261. const Comp = {
  1262. __vapor: true,
  1263. __hmrId: id,
  1264. render() {
  1265. return template('<div>foo</div>')()
  1266. },
  1267. }
  1268. createRecord(id, Comp)
  1269. const App = {
  1270. render() {
  1271. return h(Comp as any)
  1272. },
  1273. }
  1274. const root = document.createElement('div')
  1275. const app = createApp(App)
  1276. app.use(vaporInteropPlugin)
  1277. app.mount(root)
  1278. expect(root.innerHTML).toBe('<div>foo</div>')
  1279. // switch to vdom
  1280. reload(id, {
  1281. __hmrId: id,
  1282. render() {
  1283. return h('div', 'bar')
  1284. },
  1285. })
  1286. await nextTick()
  1287. expect(root.innerHTML).toBe('<div>bar</div>')
  1288. })
  1289. test('vdom -> vapor', async () => {
  1290. const id = 'vdom-to-vapor'
  1291. const Comp = {
  1292. __hmrId: id,
  1293. render() {
  1294. return h('div', 'foo')
  1295. },
  1296. }
  1297. createRecord(id, Comp)
  1298. const App = {
  1299. render() {
  1300. return h(Comp)
  1301. },
  1302. }
  1303. const root = document.createElement('div')
  1304. const app = createApp(App)
  1305. app.use(vaporInteropPlugin)
  1306. app.mount(root)
  1307. expect(root.innerHTML).toBe('<div>foo</div>')
  1308. // switch to vapor
  1309. reload(id, {
  1310. __vapor: true,
  1311. __hmrId: id,
  1312. render() {
  1313. return template('<div>bar</div>')()
  1314. },
  1315. })
  1316. await nextTick()
  1317. expect(root.innerHTML).toBe('<div>bar</div>')
  1318. })
  1319. })
  1320. })