|
|
@@ -0,0 +1,238 @@
|
|
|
+import { nextTick, watch, watchEffect } from '@vue/runtime-core'
|
|
|
+import {
|
|
|
+ reactive,
|
|
|
+ effect,
|
|
|
+ EffectScope,
|
|
|
+ onScopeDispose,
|
|
|
+ computed,
|
|
|
+ ref,
|
|
|
+ ComputedRef
|
|
|
+} from '../src'
|
|
|
+
|
|
|
+describe('reactivity/effect/scope', () => {
|
|
|
+ it('should run', () => {
|
|
|
+ const fnSpy = jest.fn(() => {})
|
|
|
+ new EffectScope().run(fnSpy)
|
|
|
+ expect(fnSpy).toHaveBeenCalledTimes(1)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should accept zero argument', () => {
|
|
|
+ const scope = new EffectScope()
|
|
|
+ expect(scope.effects.length).toBe(0)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should return run value', () => {
|
|
|
+ expect(new EffectScope().run(() => 1)).toBe(1)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should collect the effects', () => {
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ let dummy
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(1)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('stop', () => {
|
|
|
+ let dummy, doubled
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+ effect(() => (doubled = counter.num * 2))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(2)
|
|
|
+
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+
|
|
|
+ counter.num = 6
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should collect nested scope', () => {
|
|
|
+ let dummy, doubled
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+ // nested scope
|
|
|
+ new EffectScope().run(() => {
|
|
|
+ effect(() => (doubled = counter.num * 2))
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(2)
|
|
|
+ expect(scope.effects[1]).toBeInstanceOf(EffectScope)
|
|
|
+
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+
|
|
|
+ // stop the nested scope as well
|
|
|
+ scope.stop()
|
|
|
+
|
|
|
+ counter.num = 6
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('nested scope can be escaped', () => {
|
|
|
+ let dummy, doubled
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+ // nested scope
|
|
|
+ new EffectScope(true).run(() => {
|
|
|
+ effect(() => (doubled = counter.num * 2))
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(1)
|
|
|
+
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+
|
|
|
+ counter.num = 6
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+
|
|
|
+ // nested scope should not be stoped
|
|
|
+ expect(doubled).toBe(12)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('able to run the scope', () => {
|
|
|
+ let dummy, doubled
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(1)
|
|
|
+
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (doubled = counter.num * 2))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(2)
|
|
|
+
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ expect(doubled).toBe(14)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('can not run an inactive scope', () => {
|
|
|
+ let dummy, doubled
|
|
|
+ const counter = reactive({ num: 0 })
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (dummy = counter.num))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(1)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+
|
|
|
+ scope.run(() => {
|
|
|
+ effect(() => (doubled = counter.num * 2))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
|
|
+
|
|
|
+ expect(scope.effects.length).toBe(1)
|
|
|
+
|
|
|
+ counter.num = 7
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+ expect(doubled).toBe(undefined)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('should fire onDispose hook', () => {
|
|
|
+ let dummy = 0
|
|
|
+
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ onScopeDispose(() => (dummy += 1))
|
|
|
+ onScopeDispose(() => (dummy += 2))
|
|
|
+ })
|
|
|
+
|
|
|
+ scope.run(() => {
|
|
|
+ onScopeDispose(() => (dummy += 4))
|
|
|
+ })
|
|
|
+
|
|
|
+ expect(dummy).toBe(0)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+ expect(dummy).toBe(7)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('test with higher level APIs', async () => {
|
|
|
+ const r = ref(1)
|
|
|
+
|
|
|
+ const computedSpy = jest.fn()
|
|
|
+ const watchSpy = jest.fn()
|
|
|
+ const watchEffectSpy = jest.fn()
|
|
|
+
|
|
|
+ let c: ComputedRef
|
|
|
+ const scope = new EffectScope()
|
|
|
+ scope.run(() => {
|
|
|
+ c = computed(() => {
|
|
|
+ computedSpy()
|
|
|
+ return r.value + 1
|
|
|
+ })
|
|
|
+
|
|
|
+ watch(r, watchSpy)
|
|
|
+ watchEffect(() => {
|
|
|
+ watchEffectSpy()
|
|
|
+ r.value
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ c!.value // computed is lazy so trigger collection
|
|
|
+ expect(computedSpy).toHaveBeenCalledTimes(1)
|
|
|
+ expect(watchSpy).toHaveBeenCalledTimes(0)
|
|
|
+ expect(watchEffectSpy).toHaveBeenCalledTimes(1)
|
|
|
+
|
|
|
+ r.value++
|
|
|
+ c!.value
|
|
|
+ await nextTick()
|
|
|
+ expect(computedSpy).toHaveBeenCalledTimes(2)
|
|
|
+ expect(watchSpy).toHaveBeenCalledTimes(1)
|
|
|
+ expect(watchEffectSpy).toHaveBeenCalledTimes(2)
|
|
|
+
|
|
|
+ scope.stop()
|
|
|
+
|
|
|
+ r.value++
|
|
|
+ c!.value
|
|
|
+ await nextTick()
|
|
|
+ // should not trigger anymore
|
|
|
+ expect(computedSpy).toHaveBeenCalledTimes(2)
|
|
|
+ expect(watchSpy).toHaveBeenCalledTimes(1)
|
|
|
+ expect(watchEffectSpy).toHaveBeenCalledTimes(2)
|
|
|
+ })
|
|
|
+})
|