]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
chore: Merge branch 'main' into minor
authorEvan You <evan@vuejs.org>
Sat, 22 Jun 2024 13:05:15 +0000 (21:05 +0800)
committerEvan You <evan@vuejs.org>
Sat, 22 Jun 2024 13:05:15 +0000 (21:05 +0800)
1  2 
CHANGELOG.md
package.json
packages/compiler-sfc/package.json
packages/reactivity/__tests__/computed.spec.ts
packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/__tests__/hydration.spec.ts
packages/runtime-core/src/hydration.ts
packages/runtime-core/src/renderer.ts
packages/runtime-dom/__tests__/customElement.spec.ts

diff --cc CHANGELOG.md
Simple merge
diff --cc package.json
index 4205f42fe9b241e84867b173eb490b73c1fb84b9,545c053792986fa1e5d979c73b830f94790c6415..7dbd2117a956f678b6d431daa653e45bf40f9b10
@@@ -1,7 -1,7 +1,7 @@@
  {
    "private": true,
 -  "version": "3.4.30",
 +  "version": "3.5.0-alpha.2",
-   "packageManager": "pnpm@9.2.0",
+   "packageManager": "pnpm@9.3.0",
    "type": "module",
    "scripts": {
      "dev": "node scripts/dev.js",
Simple merge
index dc3df3eaaea90f4bba81622ab51e3479a3e1fd59,9a91eed638924ecc8f5d3e72c167a8bd2b2c14bd..fbe321dcaeeef25f7d4e50d2f369f29b85a331b3
@@@ -576,283 -696,75 +576,334 @@@ describe('reactivity/computed', () => 
      expect(serializeInner(root)).toBe('Hello World')
  
      v.value += ' World'
 -    expect(c.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
      await nextTick()
 -    expect(c.effect._dirtyLevel).toBe(
 -      DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
 -    )
      expect(serializeInner(root)).toBe('Hello World World World')
 -    expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
 +    // expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
 +  })
 +
 +  test('should not trigger if value did not change', () => {
 +    const src = ref(0)
 +    const c = computed(() => src.value % 2)
 +    const spy = vi.fn()
 +    effect(() => {
 +      spy(c.value)
 +    })
 +    expect(spy).toHaveBeenCalledTimes(1)
 +    src.value = 2
 +
 +    // should not trigger
 +    expect(spy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 3
 +    src.value = 5
 +    // should trigger because latest value changes
 +    expect(spy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('chained computed trigger', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(c1Spy).toHaveBeenCalledTimes(1)
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    expect(c1Spy).toHaveBeenCalledTimes(2)
 +    expect(c2Spy).toHaveBeenCalledTimes(2)
 +    expect(effectSpy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('chained computed avoid re-compute', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +    src.value = 2
 +    src.value = 4
 +    src.value = 6
 +    expect(c1Spy).toHaveBeenCalledTimes(4)
 +    // c2 should not have to re-compute because c1 did not change.
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +    // effect should not trigger because c2 did not change.
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +  })
 +
 +  test('chained computed value invalidation', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +    expect(effectSpy).toHaveBeenCalledWith(1)
 +    expect(c2.value).toBe(1)
 +
 +    expect(c1Spy).toHaveBeenCalledTimes(1)
 +    expect(c2Spy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    // value should be available sync
 +    expect(c2.value).toBe(2)
 +    expect(c2Spy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  test('sync access of invalidated chained computed should not prevent final effect from running', () => {
 +    const effectSpy = vi.fn()
 +    const c1Spy = vi.fn()
 +    const c2Spy = vi.fn()
 +
 +    const src = ref(0)
 +    const c1 = computed(() => {
 +      c1Spy()
 +      return src.value % 2
 +    })
 +    const c2 = computed(() => {
 +      c2Spy()
 +      return c1.value + 1
 +    })
 +
 +    effect(() => {
 +      effectSpy(c2.value)
 +    })
 +    expect(effectSpy).toHaveBeenCalledTimes(1)
 +
 +    src.value = 1
 +    // sync access c2
 +    c2.value
 +    expect(effectSpy).toHaveBeenCalledTimes(2)
 +  })
 +
 +  it('computed should force track in untracked zone', () => {
 +    const n = ref(0)
 +    const spy1 = vi.fn()
 +    const spy2 = vi.fn()
 +
 +    let c: ComputedRef
 +    effect(() => {
 +      spy1()
 +      pauseTracking()
 +      n.value
 +      c = computed(() => n.value + 1)
 +      // access computed now to force refresh
 +      c.value
 +      effect(() => spy2(c.value))
 +      n.value
 +      resetTracking()
 +    })
 +
 +    expect(spy1).toHaveBeenCalledTimes(1)
 +    expect(spy2).toHaveBeenCalledTimes(1)
 +
 +    n.value++
 +    // outer effect should not trigger
 +    expect(spy1).toHaveBeenCalledTimes(1)
 +    // inner effect should trigger
 +    expect(spy2).toHaveBeenCalledTimes(2)
 +  })
 +
 +  // not recommended behavior, but needed for backwards compatibility
 +  // used in VueUse asyncComputed
 +  it('computed side effect should be able trigger', () => {
 +    const a = ref(false)
 +    const b = ref(false)
 +    const c = computed(() => {
 +      a.value = true
 +      return b.value
 +    })
 +    effect(() => {
 +      if (a.value) {
 +        b.value = true
 +      }
 +    })
 +    expect(b.value).toBe(false)
 +    // accessing c triggers change
 +    c.value
 +    expect(b.value).toBe(true)
 +    expect(c.value).toBe(true)
 +  })
 +
 +  it('chained computed should work when accessed before having subs', () => {
 +    const n = ref(0)
 +    const c = computed(() => n.value)
 +    const d = computed(() => c.value + 1)
 +    const spy = vi.fn()
 +
 +    // access
 +    d.value
 +
 +    let dummy
 +    effect(() => {
 +      spy()
 +      dummy = d.value
 +    })
 +    expect(spy).toHaveBeenCalledTimes(1)
 +    expect(dummy).toBe(1)
 +
 +    n.value++
 +    expect(spy).toHaveBeenCalledTimes(2)
 +    expect(dummy).toBe(2)
 +  })
 +
 +  // #10236
 +  it('chained computed should still refresh after owner component unmount', async () => {
 +    const a = ref(0)
 +    const spy = vi.fn()
 +
 +    const Child = {
 +      setup() {
 +        const b = computed(() => a.value + 1)
 +        const c = computed(() => b.value + 1)
 +        // access
 +        c.value
 +        onUnmounted(() => spy(c.value))
 +        return () => {}
 +      },
 +    }
 +
 +    const show = ref(true)
 +    const Parent = {
 +      setup() {
 +        return () => (show.value ? h(Child) : null)
 +      },
 +    }
 +
 +    render(h(Parent), nodeOps.createElement('div'))
 +
 +    a.value++
 +    show.value = false
 +
 +    await nextTick()
 +    expect(spy).toHaveBeenCalledWith(3)
 +  })
 +
 +  // case: radix-vue `useForwardExpose` sets a template ref during mount,
 +  // and checks for the element's closest form element in a computed.
 +  // the computed is expected to only evaluate after mount.
 +  it('computed deps should only be refreshed when the subscribing effect is run, not when scheduled', async () => {
 +    const calls: string[] = []
 +    const a = ref(0)
 +    const b = computed(() => {
 +      calls.push('b eval')
 +      return a.value + 1
 +    })
 +
 +    const App = {
 +      setup() {
 +        onMounted(() => {
 +          calls.push('mounted')
 +        })
 +        return () =>
 +          h(
 +            'div',
 +            {
 +              ref: () => (a.value = 1),
 +            },
 +            b.value,
 +          )
 +      },
 +    }
 +
 +    render(h(App), nodeOps.createElement('div'))
 +
 +    await nextTick()
 +    expect(calls).toMatchObject(['b eval', 'mounted', 'b eval'])
    })
  
 -    expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
+   it('should chained computeds keep reactivity when computed effect happens', async () => {
+     const v = ref('Hello')
+     const c = computed(() => {
+       v.value += ' World'
+       return v.value
+     })
+     const d = computed(() => c.value)
+     const e = computed(() => d.value)
+     const Comp = {
+       setup: () => {
+         return () => d.value + ' | ' + e.value
+       },
+     }
+     const root = nodeOps.createElement('div')
+     render(h(Comp), root)
+     await nextTick()
+     expect(serializeInner(root)).toBe('Hello World | Hello World')
+     v.value += ' World'
+     await nextTick()
+     expect(serializeInner(root)).toBe(
+       'Hello World World World | Hello World World World',
+     )
 -    expect(c.effect._dirtyLevel).toBe(
 -      DirtyLevels.MaybeDirty_ComputedSideEffect_Origin,
 -    )
 -    expect(d.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty_ComputedSideEffect)
 -    expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
+   })
+   it('should keep dirty level when side effect computed value changed', () => {
+     const v = ref(0)
+     const c = computed(() => {
+       v.value += 1
+       return v.value
+     })
+     const d = computed(() => {
+       return { d: c.value }
+     })
+     const Comp = {
+       setup: () => {
+         return () => {
+           return [d.value.d, d.value.d]
+         }
+       },
+     }
+     const root = nodeOps.createElement('div')
+     render(h(Comp), root)
+     expect(d.value.d).toBe(1)
+     expect(serializeInner(root)).toBe('11')
+   })
    it('debug: onTrigger (ref)', () => {
      let events: DebuggerEvent[] = []
      const onTrigger = vi.fn((e: DebuggerEvent) => {
Simple merge
index d8aad9c0af1348f04b954d0c1ca9251b2f8a9e0d,62ba166b030fd64172c4434b987504c9084b3eb4..f503b2c02856a8286a071554d6fe8623da2a7ab1
@@@ -137,14 -138,10 +138,15 @@@ describe('defineCustomElement', () => 
  
    describe('props', () => {
      const E = defineCustomElement({
 -      props: ['foo', 'bar', 'bazQux', 'value'],
 +      props: {
 +        foo: [String, null],
 +        bar: Object,
 +        bazQux: null,
++        value: null,
 +      },
        render() {
          return [
 -          h('div', null, this.foo),
 +          h('div', null, this.foo || ''),
            h('div', null, this.bazQux || (this.bar && this.bar.x)),
          ]
        },