apiSetupHelpers.spec.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  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('without parent listener (local mutation)', async () => {
  255. let foo: any
  256. const update = () => {
  257. foo.value = 'bar'
  258. }
  259. const compRender = vi.fn()
  260. const Comp = defineComponent({
  261. props: ['foo'],
  262. emits: ['update:foo'],
  263. setup(props) {
  264. foo = useModel(props, 'foo')
  265. return () => {
  266. compRender()
  267. return foo.value
  268. }
  269. },
  270. })
  271. const root = nodeOps.createElement('div')
  272. // provide initial value
  273. render(h(Comp, { foo: 'initial' }), root)
  274. expect(compRender).toBeCalledTimes(1)
  275. expect(serializeInner(root)).toBe('initial')
  276. expect(foo.value).toBe('initial')
  277. update()
  278. // when parent didn't provide value, local mutation is enabled
  279. expect(foo.value).toBe('bar')
  280. await nextTick()
  281. expect(compRender).toBeCalledTimes(2)
  282. expect(serializeInner(root)).toBe('bar')
  283. })
  284. test('default value', async () => {
  285. let count: any
  286. const inc = () => {
  287. count.value++
  288. }
  289. const compRender = vi.fn()
  290. const Comp = defineComponent({
  291. props: { count: { default: 0 } },
  292. emits: ['update:count'],
  293. setup(props) {
  294. count = useModel(props, 'count')
  295. return () => {
  296. compRender()
  297. return count.value
  298. }
  299. },
  300. })
  301. const root = nodeOps.createElement('div')
  302. const updateCount = vi.fn()
  303. render(h(Comp, { 'onUpdate:count': updateCount }), root)
  304. expect(compRender).toBeCalledTimes(1)
  305. expect(serializeInner(root)).toBe('0')
  306. expect(count.value).toBe(0)
  307. inc()
  308. // when parent didn't provide value, local mutation is enabled
  309. expect(count.value).toBe(1)
  310. await nextTick()
  311. expect(updateCount).toBeCalledTimes(1)
  312. expect(compRender).toBeCalledTimes(2)
  313. expect(serializeInner(root)).toBe('1')
  314. })
  315. test('parent limiting child value', async () => {
  316. let childCount: Ref<number>
  317. const compRender = vi.fn()
  318. const Comp = defineComponent({
  319. props: ['count'],
  320. emits: ['update:count'],
  321. setup(props) {
  322. childCount = useModel(props, 'count')
  323. return () => {
  324. compRender()
  325. return childCount.value
  326. }
  327. },
  328. })
  329. const Parent = defineComponent({
  330. setup() {
  331. const count = ref(0)
  332. watch(count, () => {
  333. if (count.value < 0) {
  334. count.value = 0
  335. }
  336. })
  337. return () =>
  338. h(Comp, {
  339. count: count.value,
  340. 'onUpdate:count': val => {
  341. count.value = val
  342. },
  343. })
  344. },
  345. })
  346. const root = nodeOps.createElement('div')
  347. render(h(Parent), root)
  348. expect(serializeInner(root)).toBe('0')
  349. // child update
  350. childCount!.value = 1
  351. // not yet updated
  352. expect(childCount!.value).toBe(0)
  353. await nextTick()
  354. expect(childCount!.value).toBe(1)
  355. expect(serializeInner(root)).toBe('1')
  356. // child update to invalid value
  357. childCount!.value = -1
  358. // not yet updated
  359. expect(childCount!.value).toBe(1)
  360. await nextTick()
  361. // limited to 0 by parent
  362. expect(childCount!.value).toBe(0)
  363. expect(serializeInner(root)).toBe('0')
  364. })
  365. test('has parent value -> no parent value', async () => {
  366. let childCount: Ref<number>
  367. const compRender = vi.fn()
  368. const Comp = defineComponent({
  369. props: ['count'],
  370. emits: ['update:count'],
  371. setup(props) {
  372. childCount = useModel(props, 'count')
  373. return () => {
  374. compRender()
  375. return childCount.value
  376. }
  377. },
  378. })
  379. const toggle = ref(true)
  380. const Parent = defineComponent({
  381. setup() {
  382. const count = ref(0)
  383. return () =>
  384. toggle.value
  385. ? h(Comp, {
  386. count: count.value,
  387. 'onUpdate:count': val => {
  388. count.value = val
  389. },
  390. })
  391. : h(Comp)
  392. },
  393. })
  394. const root = nodeOps.createElement('div')
  395. render(h(Parent), root)
  396. expect(serializeInner(root)).toBe('0')
  397. // child update
  398. childCount!.value = 1
  399. // not yet updated
  400. expect(childCount!.value).toBe(0)
  401. await nextTick()
  402. expect(childCount!.value).toBe(1)
  403. expect(serializeInner(root)).toBe('1')
  404. // parent change
  405. toggle.value = false
  406. await nextTick()
  407. // localValue should be reset
  408. expect(childCount!.value).toBeUndefined()
  409. expect(serializeInner(root)).toBe('<!---->')
  410. // child local mutation should continue to work
  411. childCount!.value = 2
  412. expect(childCount!.value).toBe(2)
  413. await nextTick()
  414. expect(serializeInner(root)).toBe('2')
  415. })
  416. // #9838
  417. test('pass modelValue to slot (optimized mode) ', async () => {
  418. let foo: any
  419. const update = () => {
  420. foo.value = 'bar'
  421. }
  422. const Comp = {
  423. render(this: any) {
  424. return this.$slots.default()
  425. },
  426. }
  427. const childRender = vi.fn()
  428. const slotRender = vi.fn()
  429. const Child = defineComponent({
  430. props: ['modelValue'],
  431. emits: ['update:modelValue'],
  432. setup(props) {
  433. foo = useModel(props, 'modelValue')
  434. return () => {
  435. childRender()
  436. return (
  437. openBlock(),
  438. createElementBlock(Fragment, null, [
  439. createVNode(Comp, null, {
  440. default: () => {
  441. slotRender()
  442. return createElementVNode('div', null, foo.value)
  443. },
  444. _: 1 /* STABLE */,
  445. }),
  446. ])
  447. )
  448. }
  449. },
  450. })
  451. const msg = ref('')
  452. const setValue = vi.fn(v => (msg.value = v))
  453. const root = nodeOps.createElement('div')
  454. createApp({
  455. render() {
  456. return (
  457. openBlock(),
  458. createBlock(
  459. Child,
  460. {
  461. modelValue: msg.value,
  462. 'onUpdate:modelValue': setValue,
  463. },
  464. null,
  465. 8 /* PROPS */,
  466. ['modelValue'],
  467. )
  468. )
  469. },
  470. }).mount(root)
  471. expect(foo.value).toBe('')
  472. expect(msg.value).toBe('')
  473. expect(setValue).not.toBeCalled()
  474. expect(childRender).toBeCalledTimes(1)
  475. expect(slotRender).toBeCalledTimes(1)
  476. expect(serializeInner(root)).toBe('<div></div>')
  477. // update from child
  478. update()
  479. await nextTick()
  480. expect(msg.value).toBe('bar')
  481. expect(foo.value).toBe('bar')
  482. expect(setValue).toBeCalledTimes(1)
  483. expect(childRender).toBeCalledTimes(2)
  484. expect(slotRender).toBeCalledTimes(2)
  485. expect(serializeInner(root)).toBe('<div>bar</div>')
  486. })
  487. test('with modifiers & transformers', async () => {
  488. let childMsg: Ref<string>
  489. let childModifiers: Record<string, true | undefined>
  490. const compRender = vi.fn()
  491. const Comp = defineComponent({
  492. props: ['msg', 'msgModifiers'],
  493. emits: ['update:msg'],
  494. setup(props) {
  495. ;[childMsg, childModifiers] = useModel(props, 'msg', {
  496. get(val) {
  497. return val.toLowerCase()
  498. },
  499. set(val) {
  500. if (childModifiers.upper) {
  501. return val.toUpperCase()
  502. }
  503. },
  504. })
  505. return () => {
  506. compRender()
  507. return childMsg.value
  508. }
  509. },
  510. })
  511. const msg = ref('HI')
  512. const Parent = defineComponent({
  513. setup() {
  514. return () =>
  515. h(Comp, {
  516. msg: msg.value,
  517. msgModifiers: { upper: true },
  518. 'onUpdate:msg': val => {
  519. msg.value = val
  520. },
  521. })
  522. },
  523. })
  524. const root = nodeOps.createElement('div')
  525. render(h(Parent), root)
  526. // should be lowered
  527. expect(serializeInner(root)).toBe('hi')
  528. // child update
  529. childMsg!.value = 'Hmm'
  530. await nextTick()
  531. expect(childMsg!.value).toBe('hmm')
  532. expect(serializeInner(root)).toBe('hmm')
  533. // parent should get uppercase value
  534. expect(msg.value).toBe('HMM')
  535. // parent update
  536. msg.value = 'Ughh'
  537. await nextTick()
  538. expect(serializeInner(root)).toBe('ughh')
  539. expect(msg.value).toBe('Ughh')
  540. // child update again
  541. childMsg!.value = 'ughh'
  542. await nextTick()
  543. expect(msg.value).toBe('UGHH')
  544. })
  545. })
  546. test('createPropsRestProxy', () => {
  547. const original = shallowReactive({
  548. foo: 1,
  549. bar: 2,
  550. baz: 3,
  551. })
  552. const rest = createPropsRestProxy(original, ['foo', 'bar'])
  553. expect('foo' in rest).toBe(false)
  554. expect('bar' in rest).toBe(false)
  555. expect(rest.baz).toBe(3)
  556. expect(Object.keys(rest)).toEqual(['baz'])
  557. original.baz = 4
  558. expect(rest.baz).toBe(4)
  559. })
  560. describe('withAsyncContext', () => {
  561. // disable options API because applyOptions() also resets currentInstance
  562. // and we want to ensure the logic works even with Options API disabled.
  563. beforeEach(() => {
  564. __FEATURE_OPTIONS_API__ = false
  565. })
  566. afterEach(() => {
  567. __FEATURE_OPTIONS_API__ = true
  568. })
  569. test('basic', async () => {
  570. const spy = vi.fn()
  571. let beforeInstance: ComponentInternalInstance | null = null
  572. let afterInstance: ComponentInternalInstance | null = null
  573. let resolve: (msg: string) => void
  574. const Comp = defineComponent({
  575. async setup() {
  576. let __temp: any, __restore: any
  577. beforeInstance = getCurrentInstance()
  578. const msg =
  579. (([__temp, __restore] = withAsyncContext(
  580. () =>
  581. new Promise(r => {
  582. resolve = r
  583. }),
  584. )),
  585. (__temp = await __temp),
  586. __restore(),
  587. __temp)
  588. // register the lifecycle after an await statement
  589. onMounted(spy)
  590. afterInstance = getCurrentInstance()
  591. return () => msg
  592. },
  593. })
  594. const root = nodeOps.createElement('div')
  595. render(
  596. h(() => h(Suspense, () => h(Comp))),
  597. root,
  598. )
  599. expect(spy).not.toHaveBeenCalled()
  600. resolve!('hello')
  601. // wait a macro task tick for all micro ticks to resolve
  602. await new Promise(r => setTimeout(r))
  603. // mount hook should have been called
  604. expect(spy).toHaveBeenCalled()
  605. // should retain same instance before/after the await call
  606. expect(beforeInstance).toBe(afterInstance)
  607. expect(serializeInner(root)).toBe('hello')
  608. })
  609. test('error handling', async () => {
  610. const spy = vi.fn()
  611. let beforeInstance: ComponentInternalInstance | null = null
  612. let afterInstance: ComponentInternalInstance | null = null
  613. let reject: () => void
  614. const Comp = defineComponent({
  615. async setup() {
  616. let __temp: any, __restore: any
  617. beforeInstance = getCurrentInstance()
  618. try {
  619. ;[__temp, __restore] = withAsyncContext(
  620. () =>
  621. new Promise((_, rj) => {
  622. reject = rj
  623. }),
  624. )
  625. __temp = await __temp
  626. __restore()
  627. } catch (e: any) {
  628. // ignore
  629. }
  630. // register the lifecycle after an await statement
  631. onMounted(spy)
  632. afterInstance = getCurrentInstance()
  633. return () => ''
  634. },
  635. })
  636. const root = nodeOps.createElement('div')
  637. render(
  638. h(() => h(Suspense, () => h(Comp))),
  639. root,
  640. )
  641. expect(spy).not.toHaveBeenCalled()
  642. reject!()
  643. // wait a macro task tick for all micro ticks to resolve
  644. await new Promise(r => setTimeout(r))
  645. // mount hook should have been called
  646. expect(spy).toHaveBeenCalled()
  647. // should retain same instance before/after the await call
  648. expect(beforeInstance).toBe(afterInstance)
  649. })
  650. test('should not leak instance on multiple awaits', async () => {
  651. let resolve: (val?: any) => void
  652. let beforeInstance: ComponentInternalInstance | null = null
  653. let afterInstance: ComponentInternalInstance | null = null
  654. let inBandInstance: ComponentInternalInstance | null = null
  655. let outOfBandInstance: ComponentInternalInstance | null = null
  656. const ready = new Promise(r => {
  657. resolve = r
  658. })
  659. async function doAsyncWork() {
  660. // should still have instance
  661. inBandInstance = getCurrentInstance()
  662. await Promise.resolve()
  663. // should not leak instance
  664. outOfBandInstance = getCurrentInstance()
  665. }
  666. const Comp = defineComponent({
  667. async setup() {
  668. let __temp: any, __restore: any
  669. beforeInstance = getCurrentInstance()
  670. // first await
  671. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  672. __temp = await __temp
  673. __restore()
  674. // setup exit, instance set to null, then resumed
  675. ;[__temp, __restore] = withAsyncContext(() => doAsyncWork())
  676. __temp = await __temp
  677. __restore()
  678. afterInstance = getCurrentInstance()
  679. return () => {
  680. resolve()
  681. return ''
  682. }
  683. },
  684. })
  685. const root = nodeOps.createElement('div')
  686. render(
  687. h(() => h(Suspense, () => h(Comp))),
  688. root,
  689. )
  690. await ready
  691. expect(inBandInstance).toBe(beforeInstance)
  692. expect(outOfBandInstance).toBeNull()
  693. expect(afterInstance).toBe(beforeInstance)
  694. expect(getCurrentInstance()).toBeNull()
  695. })
  696. test('should not leak on multiple awaits + error', async () => {
  697. let resolve: (val?: any) => void
  698. const ready = new Promise(r => {
  699. resolve = r
  700. })
  701. const Comp = defineComponent({
  702. async setup() {
  703. let __temp: any, __restore: any
  704. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  705. __temp = await __temp
  706. __restore()
  707. ;[__temp, __restore] = withAsyncContext(() => Promise.reject())
  708. __temp = await __temp
  709. __restore()
  710. },
  711. render() {},
  712. })
  713. const app = createApp(() => h(Suspense, () => h(Comp)))
  714. app.config.errorHandler = () => {
  715. resolve()
  716. return false
  717. }
  718. const root = nodeOps.createElement('div')
  719. app.mount(root)
  720. await ready
  721. expect(getCurrentInstance()).toBeNull()
  722. })
  723. // #4050
  724. test('race conditions', async () => {
  725. const uids = {
  726. one: { before: NaN, after: NaN },
  727. two: { before: NaN, after: NaN },
  728. }
  729. const Comp = defineComponent({
  730. props: ['name'],
  731. async setup(props: { name: 'one' | 'two' }) {
  732. let __temp: any, __restore: any
  733. uids[props.name].before = getCurrentInstance()!.uid
  734. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  735. __temp = await __temp
  736. __restore()
  737. uids[props.name].after = getCurrentInstance()!.uid
  738. return () => ''
  739. },
  740. })
  741. const app = createApp(() =>
  742. h(Suspense, () =>
  743. h('div', [h(Comp, { name: 'one' }), h(Comp, { name: 'two' })]),
  744. ),
  745. )
  746. const root = nodeOps.createElement('div')
  747. app.mount(root)
  748. await new Promise(r => setTimeout(r))
  749. expect(uids.one.before).not.toBe(uids.two.before)
  750. expect(uids.one.before).toBe(uids.one.after)
  751. expect(uids.two.before).toBe(uids.two.after)
  752. })
  753. test('should teardown in-scope effects', async () => {
  754. let resolve: (val?: any) => void
  755. const ready = new Promise(r => {
  756. resolve = r
  757. })
  758. let c: ComputedRef
  759. const Comp = defineComponent({
  760. async setup() {
  761. let __temp: any, __restore: any
  762. ;[__temp, __restore] = withAsyncContext(() => Promise.resolve())
  763. __temp = await __temp
  764. __restore()
  765. c = computed(() => {})
  766. // register the lifecycle after an await statement
  767. onMounted(resolve)
  768. return () => ''
  769. },
  770. })
  771. const app = createApp(() => h(Suspense, () => h(Comp)))
  772. const root = nodeOps.createElement('div')
  773. app.mount(root)
  774. await ready
  775. expect(c!.effect.active).toBe(true)
  776. app.unmount()
  777. expect(c!.effect.active).toBe(false)
  778. })
  779. })
  780. })