apiSetupHelpers.spec.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  1. import {
  2. type ComponentInternalInstance,
  3. type ComputedRef,
  4. Fragment,
  5. type Ref,
  6. type SetupContext,
  7. Suspense,
  8. computed,
  9. createApp,
  10. createBlock,
  11. createElementBlock,
  12. createElementVNode,
  13. createVNode,
  14. defineComponent,
  15. getCurrentInstance,
  16. h,
  17. nextTick,
  18. nodeOps,
  19. onMounted,
  20. openBlock,
  21. ref,
  22. render,
  23. serializeInner,
  24. shallowReactive,
  25. watch,
  26. } from '@vue/runtime-test'
  27. import {
  28. createPropsRestProxy,
  29. defineEmits,
  30. defineExpose,
  31. defineProps,
  32. mergeDefaults,
  33. mergeModels,
  34. useAttrs,
  35. useModel,
  36. useSlots,
  37. withAsyncContext,
  38. withDefaults,
  39. } from '../src/apiSetupHelpers'
  40. describe('SFC <script setup> helpers', () => {
  41. test('should warn runtime usage', () => {
  42. defineProps()
  43. expect(`defineProps() is a compiler-hint`).toHaveBeenWarned()
  44. defineEmits()
  45. expect(`defineEmits() is a compiler-hint`).toHaveBeenWarned()
  46. defineExpose()
  47. expect(`defineExpose() is a compiler-hint`).toHaveBeenWarned()
  48. withDefaults({}, {})
  49. expect(`withDefaults() is a compiler-hint`).toHaveBeenWarned()
  50. })
  51. test('useSlots / useAttrs (no args)', () => {
  52. let slots: SetupContext['slots'] | undefined
  53. let attrs: SetupContext['attrs'] | undefined
  54. const Comp = {
  55. setup() {
  56. slots = useSlots()
  57. attrs = useAttrs()
  58. return () => {}
  59. },
  60. }
  61. const passedAttrs = { id: 'foo' }
  62. const passedSlots = {
  63. default: () => {},
  64. x: () => {},
  65. }
  66. render(h(Comp, passedAttrs, passedSlots), nodeOps.createElement('div'))
  67. expect(typeof slots!.default).toBe('function')
  68. expect(typeof slots!.x).toBe('function')
  69. expect(attrs).toMatchObject(passedAttrs)
  70. })
  71. test('useSlots / useAttrs (with args)', () => {
  72. let slots: SetupContext['slots'] | undefined
  73. let attrs: SetupContext['attrs'] | undefined
  74. let ctx: SetupContext | undefined
  75. const Comp = defineComponent({
  76. setup(_, _ctx) {
  77. slots = useSlots()
  78. attrs = useAttrs()
  79. ctx = _ctx
  80. return () => {}
  81. },
  82. })
  83. render(h(Comp), nodeOps.createElement('div'))
  84. expect(slots).toBe(ctx!.slots)
  85. expect(attrs).toBe(ctx!.attrs)
  86. })
  87. describe('mergeDefaults', () => {
  88. test('object syntax', () => {
  89. const merged = mergeDefaults(
  90. {
  91. foo: null,
  92. bar: { type: String, required: false },
  93. baz: String,
  94. },
  95. {
  96. foo: 1,
  97. bar: 'baz',
  98. baz: 'qux',
  99. },
  100. )
  101. expect(merged).toMatchObject({
  102. foo: { default: 1 },
  103. bar: { type: String, required: false, default: 'baz' },
  104. baz: { type: String, default: 'qux' },
  105. })
  106. })
  107. test('array syntax', () => {
  108. const merged = mergeDefaults(['foo', 'bar', 'baz'], {
  109. foo: 1,
  110. bar: 'baz',
  111. baz: 'qux',
  112. })
  113. expect(merged).toMatchObject({
  114. foo: { default: 1 },
  115. bar: { default: 'baz' },
  116. baz: { default: 'qux' },
  117. })
  118. })
  119. test('merging with skipFactory', () => {
  120. const fn = () => {}
  121. const merged = mergeDefaults(['foo', 'bar', 'baz'], {
  122. foo: fn,
  123. __skip_foo: true,
  124. })
  125. expect(merged).toMatchObject({
  126. foo: { default: fn, skipFactory: true },
  127. })
  128. })
  129. test('should warn missing', () => {
  130. mergeDefaults({}, { foo: 1 })
  131. expect(
  132. `props default key "foo" has no corresponding declaration`,
  133. ).toHaveBeenWarned()
  134. })
  135. })
  136. describe('mergeModels', () => {
  137. test('array syntax', () => {
  138. expect(mergeModels(['foo', 'bar'], ['baz'])).toMatchObject([
  139. 'foo',
  140. 'bar',
  141. 'baz',
  142. ])
  143. })
  144. test('object syntax', () => {
  145. expect(
  146. mergeModels({ foo: null, bar: { required: true } }, ['baz']),
  147. ).toMatchObject({
  148. foo: null,
  149. bar: { required: true },
  150. baz: {},
  151. })
  152. expect(
  153. mergeModels(['baz'], { foo: null, bar: { required: true } }),
  154. ).toMatchObject({
  155. foo: null,
  156. bar: { required: true },
  157. baz: {},
  158. })
  159. })
  160. test('overwrite', () => {
  161. expect(
  162. mergeModels(
  163. { foo: null, bar: { required: true } },
  164. { bar: {}, baz: {} },
  165. ),
  166. ).toMatchObject({
  167. foo: null,
  168. bar: {},
  169. baz: {},
  170. })
  171. })
  172. })
  173. describe('useModel', () => {
  174. test('basic', async () => {
  175. let foo: any
  176. const update = () => {
  177. foo.value = 'bar'
  178. }
  179. const compRender = vi.fn()
  180. const Comp = defineComponent({
  181. props: ['modelValue'],
  182. emits: ['update:modelValue'],
  183. setup(props) {
  184. foo = useModel(props, 'modelValue')
  185. return () => {
  186. compRender()
  187. return foo.value
  188. }
  189. },
  190. })
  191. const msg = ref('')
  192. const setValue = vi.fn(v => (msg.value = v))
  193. const root = nodeOps.createElement('div')
  194. createApp(() =>
  195. h(Comp, {
  196. modelValue: msg.value,
  197. 'onUpdate:modelValue': setValue,
  198. }),
  199. ).mount(root)
  200. expect(foo.value).toBe('')
  201. expect(msg.value).toBe('')
  202. expect(setValue).not.toBeCalled()
  203. expect(compRender).toBeCalledTimes(1)
  204. expect(serializeInner(root)).toBe('')
  205. // update from child
  206. update()
  207. await nextTick()
  208. expect(msg.value).toBe('bar')
  209. expect(foo.value).toBe('bar')
  210. expect(setValue).toBeCalledTimes(1)
  211. expect(compRender).toBeCalledTimes(2)
  212. expect(serializeInner(root)).toBe('bar')
  213. // update from parent
  214. msg.value = 'qux'
  215. expect(msg.value).toBe('qux')
  216. await nextTick()
  217. expect(msg.value).toBe('qux')
  218. expect(foo.value).toBe('qux')
  219. expect(setValue).toBeCalledTimes(1)
  220. expect(compRender).toBeCalledTimes(3)
  221. expect(serializeInner(root)).toBe('qux')
  222. })
  223. test('without parent value (local mutation)', async () => {
  224. let foo: any
  225. const update = () => {
  226. foo.value = 'bar'
  227. }
  228. const compRender = vi.fn()
  229. const Comp = defineComponent({
  230. props: ['foo'],
  231. emits: ['update:foo'],
  232. setup(props) {
  233. foo = useModel(props, 'foo')
  234. return () => {
  235. compRender()
  236. return foo.value
  237. }
  238. },
  239. })
  240. const root = nodeOps.createElement('div')
  241. const updateFoo = vi.fn()
  242. render(h(Comp, { 'onUpdate:foo': updateFoo }), root)
  243. expect(compRender).toBeCalledTimes(1)
  244. expect(serializeInner(root)).toBe('<!---->')
  245. expect(foo.value).toBeUndefined()
  246. update()
  247. // when parent didn't provide value, local mutation is enabled
  248. expect(foo.value).toBe('bar')
  249. await nextTick()
  250. expect(updateFoo).toBeCalledTimes(1)
  251. expect(compRender).toBeCalledTimes(2)
  252. expect(serializeInner(root)).toBe('bar')
  253. })
  254. test('default value', async () => {
  255. let count: any
  256. const inc = () => {
  257. count.value++
  258. }
  259. const compRender = vi.fn()
  260. const Comp = defineComponent({
  261. props: { count: { default: 0 } },
  262. emits: ['update:count'],
  263. setup(props) {
  264. count = useModel(props, 'count')
  265. return () => {
  266. compRender()
  267. return count.value
  268. }
  269. },
  270. })
  271. const root = nodeOps.createElement('div')
  272. const updateCount = vi.fn()
  273. render(h(Comp, { 'onUpdate:count': updateCount }), root)
  274. expect(compRender).toBeCalledTimes(1)
  275. expect(serializeInner(root)).toBe('0')
  276. expect(count.value).toBe(0)
  277. inc()
  278. // when parent didn't provide value, local mutation is enabled
  279. expect(count.value).toBe(1)
  280. await nextTick()
  281. expect(updateCount).toBeCalledTimes(1)
  282. expect(compRender).toBeCalledTimes(2)
  283. expect(serializeInner(root)).toBe('1')
  284. })
  285. test('parent limiting child value', async () => {
  286. let childCount: Ref<number>
  287. const compRender = vi.fn()
  288. const Comp = defineComponent({
  289. props: ['count'],
  290. emits: ['update:count'],
  291. setup(props) {
  292. childCount = useModel(props, 'count')
  293. return () => {
  294. compRender()
  295. return childCount.value
  296. }
  297. },
  298. })
  299. const Parent = defineComponent({
  300. setup() {
  301. const count = ref(0)
  302. watch(count, () => {
  303. if (count.value < 0) {
  304. count.value = 0
  305. }
  306. })
  307. return () =>
  308. h(Comp, {
  309. count: count.value,
  310. 'onUpdate:count': val => {
  311. count.value = val
  312. },
  313. })
  314. },
  315. })
  316. const root = nodeOps.createElement('div')
  317. render(h(Parent), root)
  318. expect(serializeInner(root)).toBe('0')
  319. // child update
  320. childCount!.value = 1
  321. // not yet updated
  322. expect(childCount!.value).toBe(0)
  323. await nextTick()
  324. expect(childCount!.value).toBe(1)
  325. expect(serializeInner(root)).toBe('1')
  326. // child update to invalid value
  327. childCount!.value = -1
  328. // not yet updated
  329. expect(childCount!.value).toBe(1)
  330. await nextTick()
  331. // limited to 0 by parent
  332. expect(childCount!.value).toBe(0)
  333. expect(serializeInner(root)).toBe('0')
  334. })
  335. test('has parent value -> no parent value', async () => {
  336. let childCount: Ref<number>
  337. const compRender = vi.fn()
  338. const Comp = defineComponent({
  339. props: ['count'],
  340. emits: ['update:count'],
  341. setup(props) {
  342. childCount = useModel(props, 'count')
  343. return () => {
  344. compRender()
  345. return childCount.value
  346. }
  347. },
  348. })
  349. const toggle = ref(true)
  350. const Parent = defineComponent({
  351. setup() {
  352. const count = ref(0)
  353. return () =>
  354. toggle.value
  355. ? h(Comp, {
  356. count: count.value,
  357. 'onUpdate:count': val => {
  358. count.value = val
  359. },
  360. })
  361. : h(Comp)
  362. },
  363. })
  364. const root = nodeOps.createElement('div')
  365. render(h(Parent), root)
  366. expect(serializeInner(root)).toBe('0')
  367. // child update
  368. childCount!.value = 1
  369. // not yet updated
  370. expect(childCount!.value).toBe(0)
  371. await nextTick()
  372. expect(childCount!.value).toBe(1)
  373. expect(serializeInner(root)).toBe('1')
  374. // parent change
  375. toggle.value = false
  376. await nextTick()
  377. // localValue should be reset
  378. expect(childCount!.value).toBeUndefined()
  379. expect(serializeInner(root)).toBe('<!---->')
  380. // child local mutation should continue to work
  381. childCount!.value = 2
  382. expect(childCount!.value).toBe(2)
  383. await nextTick()
  384. expect(serializeInner(root)).toBe('2')
  385. })
  386. // #9838
  387. test('pass modelValue to slot (optimized mode) ', async () => {
  388. let foo: any
  389. const update = () => {
  390. foo.value = 'bar'
  391. }
  392. const Comp = {
  393. render(this: any) {
  394. return this.$slots.default()
  395. },
  396. }
  397. const childRender = vi.fn()
  398. const slotRender = vi.fn()
  399. const Child = defineComponent({
  400. props: ['modelValue'],
  401. emits: ['update:modelValue'],
  402. setup(props) {
  403. foo = useModel(props, 'modelValue')
  404. return () => {
  405. childRender()
  406. return (
  407. openBlock(),
  408. createElementBlock(Fragment, null, [
  409. createVNode(Comp, null, {
  410. default: () => {
  411. slotRender()
  412. return createElementVNode('div', null, foo.value)
  413. },
  414. _: 1 /* STABLE */,
  415. }),
  416. ])
  417. )
  418. }
  419. },
  420. })
  421. const msg = ref('')
  422. const setValue = vi.fn(v => (msg.value = v))
  423. const root = nodeOps.createElement('div')
  424. createApp({
  425. render() {
  426. return (
  427. openBlock(),
  428. createBlock(
  429. Child,
  430. {
  431. modelValue: msg.value,
  432. 'onUpdate:modelValue': setValue,
  433. },
  434. null,
  435. 8 /* PROPS */,
  436. ['modelValue'],
  437. )
  438. )
  439. },
  440. }).mount(root)
  441. expect(foo.value).toBe('')
  442. expect(msg.value).toBe('')
  443. expect(setValue).not.toBeCalled()
  444. expect(childRender).toBeCalledTimes(1)
  445. expect(slotRender).toBeCalledTimes(1)
  446. expect(serializeInner(root)).toBe('<div></div>')
  447. // update from child
  448. update()
  449. await nextTick()
  450. expect(msg.value).toBe('bar')
  451. expect(foo.value).toBe('bar')
  452. expect(setValue).toBeCalledTimes(1)
  453. expect(childRender).toBeCalledTimes(2)
  454. expect(slotRender).toBeCalledTimes(2)
  455. expect(serializeInner(root)).toBe('<div>bar</div>')
  456. })
  457. test('with modifiers & transformers', async () => {
  458. let childMsg: Ref<string>
  459. let childModifiers: Record<string, true | undefined>
  460. const compRender = vi.fn()
  461. const Comp = defineComponent({
  462. props: ['msg', 'msgModifiers'],
  463. emits: ['update:msg'],
  464. setup(props) {
  465. ;[childMsg, childModifiers] = useModel(props, 'msg', {
  466. get(val) {
  467. return val.toLowerCase()
  468. },
  469. set(val) {
  470. if (childModifiers.upper) {
  471. return val.toUpperCase()
  472. }
  473. },
  474. })
  475. return () => {
  476. compRender()
  477. return childMsg.value
  478. }
  479. },
  480. })
  481. const msg = ref('HI')
  482. const Parent = defineComponent({
  483. setup() {
  484. return () =>
  485. h(Comp, {
  486. msg: msg.value,
  487. msgModifiers: { upper: true },
  488. 'onUpdate:msg': val => {
  489. msg.value = val
  490. },
  491. })
  492. },
  493. })
  494. const root = nodeOps.createElement('div')
  495. render(h(Parent), root)
  496. // should be lowered
  497. expect(serializeInner(root)).toBe('hi')
  498. // child update
  499. childMsg!.value = 'Hmm'
  500. await nextTick()
  501. expect(childMsg!.value).toBe('hmm')
  502. expect(serializeInner(root)).toBe('hmm')
  503. // parent should get uppercase value
  504. expect(msg.value).toBe('HMM')
  505. // parent update
  506. msg.value = 'Ughh'
  507. await nextTick()
  508. expect(serializeInner(root)).toBe('ughh')
  509. expect(msg.value).toBe('Ughh')
  510. // child update again
  511. childMsg!.value = 'ughh'
  512. await nextTick()
  513. expect(msg.value).toBe('UGHH')
  514. })
  515. })
  516. test('createPropsRestProxy', () => {
  517. const original = shallowReactive({
  518. foo: 1,
  519. bar: 2,
  520. baz: 3,
  521. })
  522. const rest = createPropsRestProxy(original, ['foo', 'bar'])
  523. expect('foo' in rest).toBe(false)
  524. expect('bar' in rest).toBe(false)
  525. expect(rest.baz).toBe(3)
  526. expect(Object.keys(rest)).toEqual(['baz'])
  527. original.baz = 4
  528. expect(rest.baz).toBe(4)
  529. })
  530. describe('withAsyncContext', () => {
  531. // disable options API because applyOptions() also resets currentInstance
  532. // and we want to ensure the logic works even with Options API disabled.
  533. beforeEach(() => {
  534. __FEATURE_OPTIONS_API__ = false
  535. })
  536. afterEach(() => {
  537. __FEATURE_OPTIONS_API__ = true
  538. })
  539. test('basic', async () => {
  540. const spy = vi.fn()
  541. let beforeInstance: ComponentInternalInstance | null = null
  542. let afterInstance: ComponentInternalInstance | null = null
  543. let resolve: (msg: string) => void
  544. const Comp = defineComponent({
  545. async setup() {
  546. let __temp: any, __restore: any
  547. beforeInstance = getCurrentInstance()
  548. const msg =
  549. (([__temp, __restore] = withAsyncContext(
  550. () =>
  551. new Promise(r => {
  552. resolve = r
  553. }),
  554. )),
  555. (__temp = await __temp),
  556. __restore(),
  557. __temp)
  558. // register the lifecycle after an await statement
  559. onMounted(spy)
  560. afterInstance = getCurrentInstance()
  561. return () => msg
  562. },
  563. })
  564. const root = nodeOps.createElement('div')
  565. render(
  566. h(() => h(Suspense, () => h(Comp))),
  567. root,
  568. )
  569. expect(spy).not.toHaveBeenCalled()
  570. resolve!('hello')
  571. // wait a macro task tick for all micro ticks to resolve
  572. await new Promise(r => setTimeout(r))
  573. // mount hook should have been called
  574. expect(spy).toHaveBeenCalled()
  575. // should retain same instance before/after the await call
  576. expect(beforeInstance).toBe(afterInstance)
  577. expect(serializeInner(root)).toBe('hello')
  578. })
  579. test('error handling', async () => {
  580. const spy = vi.fn()
  581. let beforeInstance: ComponentInternalInstance | null = null
  582. let afterInstance: ComponentInternalInstance | null = null
  583. let reject: () => void
  584. const Comp = defineComponent({
  585. async setup() {
  586. let __temp: any, __restore: any
  587. beforeInstance = getCurrentInstance()
  588. try {
  589. ;[__temp, __restore] = withAsyncContext(
  590. () =>
  591. new Promise((_, rj) => {
  592. reject = rj
  593. }),
  594. )
  595. __temp = await __temp
  596. __restore()
  597. } catch (e: any) {
  598. // ignore
  599. }
  600. // register the lifecycle after an await statement
  601. onMounted(spy)
  602. afterInstance = getCurrentInstance()
  603. return () => ''
  604. },
  605. })
  606. const root = nodeOps.createElement('div')
  607. render(
  608. h(() => h(Suspense, () => h(Comp))),
  609. root,
  610. )
  611. expect(spy).not.toHaveBeenCalled()
  612. reject!()
  613. // wait a macro task tick for all micro ticks to resolve
  614. await new Promise(r => setTimeout(r))
  615. // mount hook should have been called
  616. expect(spy).toHaveBeenCalled()
  617. // should retain same instance before/after the await call
  618. expect(beforeInstance).toBe(afterInstance)
  619. })
  620. test('should not leak instance on multiple awaits', async () => {
  621. let resolve: (val?: any) => void
  622. let beforeInstance: ComponentInternalInstance | null = null
  623. let afterInstance: ComponentInternalInstance | null = null
  624. let inBandInstance: ComponentInternalInstance | null = null
  625. let outOfBandInstance: ComponentInternalInstance | null = null
  626. const ready = new Promise(r => {
  627. resolve = r
  628. })
  629. async function doAsyncWork() {
  630. // should still have instance
  631. inBandInstance = getCurrentInstance()
  632. await Promise.resolve()
  633. // should not leak instance
  634. outOfBandInstance = getCurrentInstance()
  635. }
  636. const Comp = defineComponent({
  637. async setup() {
  638. let __temp: any, __restore: any
  639. beforeInstance = getCurrentInstance()
  640. // first await
  641. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  642. __temp = await __temp
  643. __restore()
  644. // setup exit, instance set to null, then resumed
  645. ;[__temp, __restore] = withAsyncContext(() => doAsyncWork())
  646. __temp = await __temp
  647. __restore()
  648. afterInstance = getCurrentInstance()
  649. return () => {
  650. resolve()
  651. return ''
  652. }
  653. },
  654. })
  655. const root = nodeOps.createElement('div')
  656. render(
  657. h(() => h(Suspense, () => h(Comp))),
  658. root,
  659. )
  660. await ready
  661. expect(inBandInstance).toBe(beforeInstance)
  662. expect(outOfBandInstance).toBeNull()
  663. expect(afterInstance).toBe(beforeInstance)
  664. expect(getCurrentInstance()).toBeNull()
  665. })
  666. test('should not leak on multiple awaits + error', async () => {
  667. let resolve: (val?: any) => void
  668. const ready = new Promise(r => {
  669. resolve = r
  670. })
  671. const Comp = defineComponent({
  672. async setup() {
  673. let __temp: any, __restore: any
  674. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  675. __temp = await __temp
  676. __restore()
  677. ;[__temp, __restore] = withAsyncContext(() => Promise.reject())
  678. __temp = await __temp
  679. __restore()
  680. },
  681. render() {},
  682. })
  683. const app = createApp(() => h(Suspense, () => h(Comp)))
  684. app.config.errorHandler = () => {
  685. resolve()
  686. return false
  687. }
  688. const root = nodeOps.createElement('div')
  689. app.mount(root)
  690. await ready
  691. expect(getCurrentInstance()).toBeNull()
  692. })
  693. // #4050
  694. test('race conditions', async () => {
  695. const uids = {
  696. one: { before: NaN, after: NaN },
  697. two: { before: NaN, after: NaN },
  698. }
  699. const Comp = defineComponent({
  700. props: ['name'],
  701. async setup(props: { name: 'one' | 'two' }) {
  702. let __temp: any, __restore: any
  703. uids[props.name].before = getCurrentInstance()!.uid
  704. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  705. __temp = await __temp
  706. __restore()
  707. uids[props.name].after = getCurrentInstance()!.uid
  708. return () => ''
  709. },
  710. })
  711. const app = createApp(() =>
  712. h(Suspense, () =>
  713. h('div', [h(Comp, { name: 'one' }), h(Comp, { name: 'two' })]),
  714. ),
  715. )
  716. const root = nodeOps.createElement('div')
  717. app.mount(root)
  718. await new Promise(r => setTimeout(r))
  719. expect(uids.one.before).not.toBe(uids.two.before)
  720. expect(uids.one.before).toBe(uids.one.after)
  721. expect(uids.two.before).toBe(uids.two.after)
  722. })
  723. test('should teardown in-scope effects', async () => {
  724. let resolve: (val?: any) => void
  725. const ready = new Promise(r => {
  726. resolve = r
  727. })
  728. let c: ComputedRef
  729. const Comp = defineComponent({
  730. async setup() {
  731. let __temp: any, __restore: any
  732. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  733. __temp = await __temp
  734. __restore()
  735. c = computed(() => {})
  736. // register the lifecycle after an await statement
  737. onMounted(resolve)
  738. return () => ''
  739. },
  740. })
  741. const app = createApp(() => h(Suspense, () => h(Comp)))
  742. const root = nodeOps.createElement('div')
  743. app.mount(root)
  744. await ready
  745. expect(c!.effect.active).toBe(true)
  746. app.unmount()
  747. expect(c!.effect.active).toBe(false)
  748. })
  749. })
  750. })