hmr.spec.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760
  1. import type { HMRRuntime } from '../src/hmr'
  2. import '../src/hmr'
  3. import type { ComponentOptions, InternalRenderFunction } from '../src/component'
  4. import {
  5. type TestElement,
  6. h,
  7. nextTick,
  8. nodeOps,
  9. render,
  10. serializeInner,
  11. triggerEvent,
  12. } from '@vue/runtime-test'
  13. import * as runtimeTest from '@vue/runtime-test'
  14. import { createApp, registerRuntimeCompiler } from '@vue/runtime-test'
  15. import { baseCompile } from '@vue/compiler-core'
  16. declare var __VUE_HMR_RUNTIME__: HMRRuntime
  17. const { createRecord, rerender, reload } = __VUE_HMR_RUNTIME__
  18. registerRuntimeCompiler(compileToFunction)
  19. function compileToFunction(template: string) {
  20. const { code } = baseCompile(template, { hoistStatic: true, hmr: true })
  21. const render = new Function('Vue', code)(
  22. runtimeTest,
  23. ) as InternalRenderFunction
  24. render._rc = true // isRuntimeCompiled
  25. return render
  26. }
  27. describe('hot module replacement', () => {
  28. test('inject global runtime', () => {
  29. expect(createRecord).toBeDefined()
  30. expect(rerender).toBeDefined()
  31. expect(reload).toBeDefined()
  32. })
  33. test('createRecord', () => {
  34. expect(createRecord('test1', {})).toBe(true)
  35. // if id has already been created, should return false
  36. expect(createRecord('test1', {})).toBe(false)
  37. })
  38. test('rerender', async () => {
  39. const root = nodeOps.createElement('div')
  40. const parentId = 'test2-parent'
  41. const childId = 'test2-child'
  42. const Child: ComponentOptions = {
  43. __hmrId: childId,
  44. render: compileToFunction(`<div><slot/></div>`),
  45. }
  46. createRecord(childId, Child)
  47. const Parent: ComponentOptions = {
  48. __hmrId: parentId,
  49. data() {
  50. return { count: 0 }
  51. },
  52. components: { Child },
  53. render: compileToFunction(
  54. `<div @click="count++">{{ count }}<Child>{{ count }}</Child></div>`,
  55. ),
  56. }
  57. createRecord(parentId, Parent)
  58. render(h(Parent), root)
  59. expect(serializeInner(root)).toBe(`<div>0<div>0</div></div>`)
  60. // Perform some state change. This change should be preserved after the
  61. // re-render!
  62. triggerEvent(root.children[0] as TestElement, 'click')
  63. await nextTick()
  64. expect(serializeInner(root)).toBe(`<div>1<div>1</div></div>`)
  65. // // Update text while preserving state
  66. rerender(
  67. parentId,
  68. compileToFunction(
  69. `<div @click="count++">{{ count }}!<Child>{{ count }}</Child></div>`,
  70. ),
  71. )
  72. expect(serializeInner(root)).toBe(`<div>1!<div>1</div></div>`)
  73. // Should force child update on slot content change
  74. rerender(
  75. parentId,
  76. compileToFunction(
  77. `<div @click="count++">{{ count }}!<Child>{{ count }}!</Child></div>`,
  78. ),
  79. )
  80. expect(serializeInner(root)).toBe(`<div>1!<div>1!</div></div>`)
  81. // Should force update element children despite block optimization
  82. rerender(
  83. parentId,
  84. compileToFunction(
  85. `<div @click="count++">{{ count }}<span>{{ count }}</span>
  86. <Child>{{ count }}!</Child>
  87. </div>`,
  88. ),
  89. )
  90. expect(serializeInner(root)).toBe(`<div>1<span>1</span><div>1!</div></div>`)
  91. // Should force update child slot elements
  92. rerender(
  93. parentId,
  94. compileToFunction(
  95. `<div @click="count++">
  96. <Child><span>{{ count }}</span></Child>
  97. </div>`,
  98. ),
  99. )
  100. expect(serializeInner(root)).toBe(`<div><div><span>1</span></div></div>`)
  101. })
  102. test('reload', async () => {
  103. const root = nodeOps.createElement('div')
  104. const childId = 'test3-child'
  105. const unmountSpy = vi.fn()
  106. const mountSpy = vi.fn()
  107. const Child: ComponentOptions = {
  108. __hmrId: childId,
  109. data() {
  110. return { count: 0 }
  111. },
  112. unmounted: unmountSpy,
  113. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  114. }
  115. createRecord(childId, Child)
  116. const Parent: ComponentOptions = {
  117. render: () => h(Child),
  118. }
  119. render(h(Parent), root)
  120. expect(serializeInner(root)).toBe(`<div>0</div>`)
  121. reload(childId, {
  122. __hmrId: childId,
  123. data() {
  124. return { count: 1 }
  125. },
  126. mounted: mountSpy,
  127. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  128. })
  129. await nextTick()
  130. expect(serializeInner(root)).toBe(`<div>1</div>`)
  131. expect(unmountSpy).toHaveBeenCalledTimes(1)
  132. expect(mountSpy).toHaveBeenCalledTimes(1)
  133. })
  134. // #7042
  135. test('reload KeepAlive slot', async () => {
  136. const root = nodeOps.createElement('div')
  137. const childId = 'test-child-keep-alive'
  138. const unmountSpy = vi.fn()
  139. const mountSpy = vi.fn()
  140. const activeSpy = vi.fn()
  141. const deactiveSpy = vi.fn()
  142. const Child: ComponentOptions = {
  143. __hmrId: childId,
  144. data() {
  145. return { count: 0 }
  146. },
  147. unmounted: unmountSpy,
  148. render: compileToFunction(`<div>{{ count }}</div>`),
  149. }
  150. createRecord(childId, Child)
  151. const Parent: ComponentOptions = {
  152. components: { Child },
  153. data() {
  154. return { toggle: true }
  155. },
  156. render: compileToFunction(
  157. `<button @click="toggle = !toggle" />
  158. <KeepAlive><Child v-if="toggle" /></KeepAlive>`,
  159. ),
  160. }
  161. render(h(Parent), root)
  162. expect(serializeInner(root)).toBe(`<button></button><div>0</div>`)
  163. reload(childId, {
  164. __hmrId: childId,
  165. data() {
  166. return { count: 1 }
  167. },
  168. mounted: mountSpy,
  169. unmounted: unmountSpy,
  170. activated: activeSpy,
  171. deactivated: deactiveSpy,
  172. render: compileToFunction(`<div>{{ count }}</div>`),
  173. })
  174. await nextTick()
  175. expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
  176. expect(unmountSpy).toHaveBeenCalledTimes(1)
  177. expect(mountSpy).toHaveBeenCalledTimes(1)
  178. expect(activeSpy).toHaveBeenCalledTimes(1)
  179. expect(deactiveSpy).toHaveBeenCalledTimes(0)
  180. // should not unmount when toggling
  181. triggerEvent(root.children[1] as TestElement, 'click')
  182. await nextTick()
  183. expect(unmountSpy).toHaveBeenCalledTimes(1)
  184. expect(mountSpy).toHaveBeenCalledTimes(1)
  185. expect(activeSpy).toHaveBeenCalledTimes(1)
  186. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  187. // should not mount when toggling
  188. triggerEvent(root.children[1] as TestElement, 'click')
  189. await nextTick()
  190. expect(unmountSpy).toHaveBeenCalledTimes(1)
  191. expect(mountSpy).toHaveBeenCalledTimes(1)
  192. expect(activeSpy).toHaveBeenCalledTimes(2)
  193. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  194. })
  195. // #7121
  196. test('reload KeepAlive slot in Transition', async () => {
  197. const root = nodeOps.createElement('div')
  198. const childId = 'test-transition-keep-alive-reload'
  199. const unmountSpy = vi.fn()
  200. const mountSpy = vi.fn()
  201. const activeSpy = vi.fn()
  202. const deactiveSpy = vi.fn()
  203. const Child: ComponentOptions = {
  204. __hmrId: childId,
  205. data() {
  206. return { count: 0 }
  207. },
  208. unmounted: unmountSpy,
  209. render: compileToFunction(`<div>{{ count }}</div>`),
  210. }
  211. createRecord(childId, Child)
  212. const Parent: ComponentOptions = {
  213. components: { Child },
  214. data() {
  215. return { toggle: true }
  216. },
  217. render: compileToFunction(
  218. `<button @click="toggle = !toggle" />
  219. <BaseTransition>
  220. <KeepAlive><Child v-if="toggle" /></KeepAlive>
  221. </BaseTransition>`,
  222. ),
  223. }
  224. render(h(Parent), root)
  225. expect(serializeInner(root)).toBe(`<button></button><div>0</div>`)
  226. reload(childId, {
  227. __hmrId: childId,
  228. data() {
  229. return { count: 1 }
  230. },
  231. mounted: mountSpy,
  232. unmounted: unmountSpy,
  233. activated: activeSpy,
  234. deactivated: deactiveSpy,
  235. render: compileToFunction(`<div>{{ count }}</div>`),
  236. })
  237. await nextTick()
  238. expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
  239. expect(unmountSpy).toHaveBeenCalledTimes(1)
  240. expect(mountSpy).toHaveBeenCalledTimes(1)
  241. expect(activeSpy).toHaveBeenCalledTimes(1)
  242. expect(deactiveSpy).toHaveBeenCalledTimes(0)
  243. // should not unmount when toggling
  244. triggerEvent(root.children[1] as TestElement, 'click')
  245. await nextTick()
  246. expect(serializeInner(root)).toBe(`<button></button><!--v-if-->`)
  247. expect(unmountSpy).toHaveBeenCalledTimes(1)
  248. expect(mountSpy).toHaveBeenCalledTimes(1)
  249. expect(activeSpy).toHaveBeenCalledTimes(1)
  250. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  251. // should not mount when toggling
  252. triggerEvent(root.children[1] as TestElement, 'click')
  253. await nextTick()
  254. expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
  255. expect(unmountSpy).toHaveBeenCalledTimes(1)
  256. expect(mountSpy).toHaveBeenCalledTimes(1)
  257. expect(activeSpy).toHaveBeenCalledTimes(2)
  258. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  259. })
  260. test('reload KeepAlive slot in Transition with out-in', async () => {
  261. const root = nodeOps.createElement('div')
  262. const childId = 'test-transition-keep-alive-reload-with-out-in'
  263. const unmountSpy = vi.fn()
  264. const mountSpy = vi.fn()
  265. const activeSpy = vi.fn()
  266. const deactiveSpy = vi.fn()
  267. const Child: ComponentOptions = {
  268. __hmrId: childId,
  269. name: 'original',
  270. data() {
  271. return { count: 0 }
  272. },
  273. unmounted: unmountSpy,
  274. render: compileToFunction(`<div>{{ count }}</div>`),
  275. }
  276. createRecord(childId, Child)
  277. const Parent: ComponentOptions = {
  278. components: { Child },
  279. data() {
  280. return { toggle: true }
  281. },
  282. methods: {
  283. // @ts-expect-error
  284. onLeave(_, done) {
  285. setTimeout(done, 0)
  286. },
  287. },
  288. render: compileToFunction(
  289. `<button @click="toggle = !toggle" />
  290. <BaseTransition mode="out-in" @leave="onLeave">
  291. <KeepAlive><Child v-if="toggle" /></KeepAlive>
  292. </BaseTransition>`,
  293. ),
  294. }
  295. render(h(Parent), root)
  296. expect(serializeInner(root)).toBe(`<button></button><div>0</div>`)
  297. reload(childId, {
  298. __hmrId: childId,
  299. name: 'updated',
  300. data() {
  301. return { count: 1 }
  302. },
  303. mounted: mountSpy,
  304. unmounted: unmountSpy,
  305. activated: activeSpy,
  306. deactivated: deactiveSpy,
  307. render: compileToFunction(`<div>{{ count }}</div>`),
  308. })
  309. await nextTick()
  310. await new Promise(r => setTimeout(r, 0))
  311. expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
  312. expect(unmountSpy).toHaveBeenCalledTimes(1)
  313. expect(mountSpy).toHaveBeenCalledTimes(1)
  314. expect(activeSpy).toHaveBeenCalledTimes(1)
  315. expect(deactiveSpy).toHaveBeenCalledTimes(0)
  316. // should not unmount when toggling
  317. triggerEvent(root.children[1] as TestElement, 'click')
  318. await nextTick()
  319. await new Promise(r => setTimeout(r, 0))
  320. expect(serializeInner(root)).toBe(`<button></button><!---->`)
  321. expect(unmountSpy).toHaveBeenCalledTimes(1)
  322. expect(mountSpy).toHaveBeenCalledTimes(1)
  323. expect(activeSpy).toHaveBeenCalledTimes(1)
  324. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  325. // should not mount when toggling
  326. triggerEvent(root.children[1] as TestElement, 'click')
  327. await nextTick()
  328. expect(serializeInner(root)).toBe(`<button></button><div>1</div>`)
  329. expect(unmountSpy).toHaveBeenCalledTimes(1)
  330. expect(mountSpy).toHaveBeenCalledTimes(1)
  331. expect(activeSpy).toHaveBeenCalledTimes(2)
  332. expect(deactiveSpy).toHaveBeenCalledTimes(1)
  333. })
  334. test('reload class component', async () => {
  335. const root = nodeOps.createElement('div')
  336. const childId = 'test4-child'
  337. const unmountSpy = vi.fn()
  338. const mountSpy = vi.fn()
  339. class Child {
  340. static __vccOpts: ComponentOptions = {
  341. __hmrId: childId,
  342. data() {
  343. return { count: 0 }
  344. },
  345. unmounted: unmountSpy,
  346. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  347. }
  348. }
  349. createRecord(childId, Child)
  350. const Parent: ComponentOptions = {
  351. render: () => h(Child),
  352. }
  353. render(h(Parent), root)
  354. expect(serializeInner(root)).toBe(`<div>0</div>`)
  355. class UpdatedChild {
  356. static __vccOpts: ComponentOptions = {
  357. __hmrId: childId,
  358. data() {
  359. return { count: 1 }
  360. },
  361. mounted: mountSpy,
  362. render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
  363. }
  364. }
  365. reload(childId, UpdatedChild)
  366. await nextTick()
  367. expect(serializeInner(root)).toBe(`<div>1</div>`)
  368. expect(unmountSpy).toHaveBeenCalledTimes(1)
  369. expect(mountSpy).toHaveBeenCalledTimes(1)
  370. })
  371. // #1156 - static nodes should retain DOM element reference across updates
  372. // when HMR is active
  373. test('static el reference', async () => {
  374. const root = nodeOps.createElement('div')
  375. const id = 'test-static-el'
  376. const template = `<div>
  377. <div>{{ count }}</div>
  378. <button @click="count++">++</button>
  379. </div>`
  380. const Comp: ComponentOptions = {
  381. __hmrId: id,
  382. data() {
  383. return { count: 0 }
  384. },
  385. render: compileToFunction(template),
  386. }
  387. createRecord(id, Comp)
  388. render(h(Comp), root)
  389. expect(serializeInner(root)).toBe(
  390. `<div><div>0</div><button>++</button></div>`,
  391. )
  392. // 1. click to trigger update
  393. triggerEvent((root as any).children[0].children[1], 'click')
  394. await nextTick()
  395. expect(serializeInner(root)).toBe(
  396. `<div><div>1</div><button>++</button></div>`,
  397. )
  398. // 2. trigger HMR
  399. rerender(
  400. id,
  401. compileToFunction(template.replace(`<button`, `<button class="foo"`)),
  402. )
  403. expect(serializeInner(root)).toBe(
  404. `<div><div>1</div><button class="foo">++</button></div>`,
  405. )
  406. })
  407. // #1157 - component should force full props update when HMR is active
  408. test('force update child component w/ static props', () => {
  409. const root = nodeOps.createElement('div')
  410. const parentId = 'test-force-props-parent'
  411. const childId = 'test-force-props-child'
  412. const Child: ComponentOptions = {
  413. __hmrId: childId,
  414. props: {
  415. msg: String,
  416. },
  417. render: compileToFunction(`<div>{{ msg }}</div>`),
  418. }
  419. createRecord(childId, Child)
  420. const Parent: ComponentOptions = {
  421. __hmrId: parentId,
  422. components: { Child },
  423. render: compileToFunction(`<Child msg="foo" />`),
  424. }
  425. createRecord(parentId, Parent)
  426. render(h(Parent), root)
  427. expect(serializeInner(root)).toBe(`<div>foo</div>`)
  428. rerender(parentId, compileToFunction(`<Child msg="bar" />`))
  429. expect(serializeInner(root)).toBe(`<div>bar</div>`)
  430. })
  431. // #1305 - component should remove class
  432. test('remove static class from parent', () => {
  433. const root = nodeOps.createElement('div')
  434. const parentId = 'test-force-class-parent'
  435. const childId = 'test-force-class-child'
  436. const Child: ComponentOptions = {
  437. __hmrId: childId,
  438. render: compileToFunction(`<div>child</div>`),
  439. }
  440. createRecord(childId, Child)
  441. const Parent: ComponentOptions = {
  442. __hmrId: parentId,
  443. components: { Child },
  444. render: compileToFunction(`<Child class="test" />`),
  445. }
  446. createRecord(parentId, Parent)
  447. render(h(Parent), root)
  448. expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
  449. rerender(parentId, compileToFunction(`<Child/>`))
  450. expect(serializeInner(root)).toBe(`<div>child</div>`)
  451. })
  452. test('rerender if any parent in the parent chain', () => {
  453. const root = nodeOps.createElement('div')
  454. const parent = 'test-force-props-parent-'
  455. const childId = 'test-force-props-child'
  456. const numberOfParents = 5
  457. const Child: ComponentOptions = {
  458. __hmrId: childId,
  459. render: compileToFunction(`<div>child</div>`),
  460. }
  461. createRecord(childId, Child)
  462. const components: ComponentOptions[] = []
  463. for (let i = 0; i < numberOfParents; i++) {
  464. const parentId = `${parent}${i}`
  465. const parentComp: ComponentOptions = {
  466. __hmrId: parentId,
  467. }
  468. components.push(parentComp)
  469. if (i === 0) {
  470. parentComp.render = compileToFunction(`<Child />`)
  471. parentComp.components = {
  472. Child,
  473. }
  474. } else {
  475. parentComp.render = compileToFunction(`<Parent />`)
  476. parentComp.components = {
  477. Parent: components[i - 1],
  478. }
  479. }
  480. createRecord(parentId, parentComp)
  481. }
  482. const last = components[components.length - 1]
  483. render(h(last), root)
  484. expect(serializeInner(root)).toBe(`<div>child</div>`)
  485. rerender(last.__hmrId!, compileToFunction(`<Parent class="test"/>`))
  486. expect(serializeInner(root)).toBe(`<div class="test">child</div>`)
  487. })
  488. // #3302
  489. test('rerender with Teleport', () => {
  490. const root = nodeOps.createElement('div')
  491. const target = nodeOps.createElement('div')
  492. const parentId = 'parent-teleport'
  493. const Child: ComponentOptions = {
  494. data() {
  495. return {
  496. // style is used to ensure that the div tag will be tracked by Teleport
  497. style: {},
  498. target,
  499. }
  500. },
  501. render: compileToFunction(`
  502. <teleport :to="target">
  503. <div :style="style">
  504. <slot/>
  505. </div>
  506. </teleport>
  507. `),
  508. }
  509. const Parent: ComponentOptions = {
  510. __hmrId: parentId,
  511. components: { Child },
  512. render: compileToFunction(`
  513. <Child>
  514. <template #default>
  515. <div>1</div>
  516. </template>
  517. </Child>
  518. `),
  519. }
  520. createRecord(parentId, Parent)
  521. render(h(Parent), root)
  522. expect(serializeInner(root)).toBe(
  523. `<!--teleport start--><!--teleport end-->`,
  524. )
  525. expect(serializeInner(target)).toBe(`<div style={}><div>1</div></div>`)
  526. rerender(
  527. parentId,
  528. compileToFunction(`
  529. <Child>
  530. <template #default>
  531. <div>1</div>
  532. <div>2</div>
  533. </template>
  534. </Child>
  535. `),
  536. )
  537. expect(serializeInner(root)).toBe(
  538. `<!--teleport start--><!--teleport end-->`,
  539. )
  540. expect(serializeInner(target)).toBe(
  541. `<div style={}><div>1</div><div>2</div></div>`,
  542. )
  543. })
  544. // #4174
  545. test('with global mixins', async () => {
  546. const childId = 'hmr-global-mixin'
  547. const createSpy1 = vi.fn()
  548. const createSpy2 = vi.fn()
  549. const Child: ComponentOptions = {
  550. __hmrId: childId,
  551. created: createSpy1,
  552. render() {
  553. return h('div')
  554. },
  555. }
  556. createRecord(childId, Child)
  557. const Parent: ComponentOptions = {
  558. render: () => h(Child),
  559. }
  560. const app = createApp(Parent)
  561. app.mixin({})
  562. const root = nodeOps.createElement('div')
  563. app.mount(root)
  564. expect(createSpy1).toHaveBeenCalledTimes(1)
  565. expect(createSpy2).toHaveBeenCalledTimes(0)
  566. reload(childId, {
  567. __hmrId: childId,
  568. created: createSpy2,
  569. render() {
  570. return h('div')
  571. },
  572. })
  573. await nextTick()
  574. expect(createSpy1).toHaveBeenCalledTimes(1)
  575. expect(createSpy2).toHaveBeenCalledTimes(1)
  576. })
  577. // #4757
  578. test('rerender for component that has no active instance yet', () => {
  579. const id = 'no-active-instance-rerender'
  580. const Foo: ComponentOptions = {
  581. __hmrId: id,
  582. render: () => 'foo',
  583. }
  584. createRecord(id, Foo)
  585. rerender(id, () => 'bar')
  586. const root = nodeOps.createElement('div')
  587. render(h(Foo), root)
  588. expect(serializeInner(root)).toBe('bar')
  589. })
  590. test('reload for component that has no active instance yet', () => {
  591. const id = 'no-active-instance-reload'
  592. const Foo: ComponentOptions = {
  593. __hmrId: id,
  594. render: () => 'foo',
  595. }
  596. createRecord(id, Foo)
  597. reload(id, {
  598. __hmrId: id,
  599. render: () => 'bar',
  600. })
  601. const root = nodeOps.createElement('div')
  602. render(h(Foo), root)
  603. expect(serializeInner(root)).toBe('bar')
  604. })
  605. // #7155 - force HMR on slots content update
  606. test('force update slot content change', () => {
  607. const root = nodeOps.createElement('div')
  608. const parentId = 'test-force-computed-parent'
  609. const childId = 'test-force-computed-child'
  610. const Child: ComponentOptions = {
  611. __hmrId: childId,
  612. computed: {
  613. slotContent() {
  614. return this.$slots.default?.()
  615. },
  616. },
  617. render: compileToFunction(`<component :is="() => slotContent" />`),
  618. }
  619. createRecord(childId, Child)
  620. const Parent: ComponentOptions = {
  621. __hmrId: parentId,
  622. components: { Child },
  623. render: compileToFunction(`<Child>1</Child>`),
  624. }
  625. createRecord(parentId, Parent)
  626. render(h(Parent), root)
  627. expect(serializeInner(root)).toBe(`1`)
  628. rerender(parentId, compileToFunction(`<Child>2</Child>`))
  629. expect(serializeInner(root)).toBe(`2`)
  630. })
  631. // #6978, #7138, #7114
  632. test('hoisted children array inside v-for', () => {
  633. const root = nodeOps.createElement('div')
  634. const appId = 'test-app-id'
  635. const App: ComponentOptions = {
  636. __hmrId: appId,
  637. render: compileToFunction(
  638. `<div v-for="item of 2">
  639. <div>1</div>
  640. </div>
  641. <p>2</p>
  642. <p>3</p>`,
  643. ),
  644. }
  645. createRecord(appId, App)
  646. render(h(App), root)
  647. expect(serializeInner(root)).toBe(
  648. `<div><div>1</div></div><div><div>1</div></div><p>2</p><p>3</p>`,
  649. )
  650. // move the <p>3</p> into the <div>1</div>
  651. rerender(
  652. appId,
  653. compileToFunction(
  654. `<div v-for="item of 2">
  655. <div>1<p>3</p></div>
  656. </div>
  657. <p>2</p>`,
  658. ),
  659. )
  660. expect(serializeInner(root)).toBe(
  661. `<div><div>1<p>3</p></div></div><div><div>1<p>3</p></div></div><p>2</p>`,
  662. )
  663. })
  664. })