Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async watcher in some cases called twice after setProps. #1379

Open
motz-art opened this issue Dec 18, 2019 · 5 comments
Open

Async watcher in some cases called twice after setProps. #1379

motz-art opened this issue Dec 18, 2019 · 5 comments
Projects

Comments

@motz-art
Copy link

@motz-art motz-art commented Dec 18, 2019

Version

1.0.0-beta.30

Reproduction link

https://github.com/motz-art/vue-test-utils-watcher-called-twice

Steps to reproduce

After opening repo run

npm i
npm run test:unit

What is expected?

After call to setProps component value watch handler should be called once.

This will match behavior of such component in a real browser.

And console output should be like that:

undefined
setting props
props are set.
good

As a result test will also pass.

What is actually happening?

After a call to setProps there is two calls to value watch handler, first with the value 'good' that is supplied in setProps and second with undefined.

See console output:

undefined
setting props
props are set.
good
undefined

As second call happens right before Promise is resolved, it sets delayedValue to undefined and that leads to failed test.


I don't know why but removing any of the if statements fixes the issue. As well as removing immediate: true from value watcher.

Checked this test in @vue/test-utils@1.0.0-beta.29 there is 3 calls to watch handler, so console output looks like:

undefined
setting props
props are set.
good
undefined
good
@JessicaSachs
Copy link
Collaborator

@JessicaSachs JessicaSachs commented Dec 19, 2019

There were a lot of async changes with setProps, setData, and trigger in beta.30. Would you like to take a crack at this bug?

@JessicaSachs JessicaSachs added this to To do in 1.0 Dec 21, 2019
@dobromir-hristov
Copy link
Collaborator

@dobromir-hristov dobromir-hristov commented Jan 5, 2020

Just wrote a unit test for this and it passes, the watcher's handler is called properly.

 it.only('runs the watcher only once', async () => {
    const listener = () => Promise.resolve()
    const spy = sinon.spy(listener)
    const TestComponent = {
      template: '<div/>',
      props: ['prop1'],
      watch: {
        prop1: {
          immediate: true,
          handler: spy
        }
      }
    }
    const wrapper = mountingMethod(TestComponent)
    await wrapper.vm.$nextTick()
    expect(spy.callCount, 'To be called initially once').to.equal(1)

    wrapper.setProps({
      prop1: 'some Data'
    })
    await wrapper.vm.$nextTick()
    expect(spy.callCount, 'to have been called an extra one time after using setProps').to.equal(2)
    expect(spy.getCall(1).calledWith('some Data', undefined)).to.be.true
  })
@guilhermewaess
Copy link

@guilhermewaess guilhermewaess commented Apr 1, 2020

I have the following scenario which might be the same problem. If you guys have any idea how to work around it.

And also if I can help with something else, just let me know.

The code:

AppSnackbar:

@Component
export default class AppSnackbar extends Vue {
  visiblityTimer: NodeJS.Timeout | null = null

  @Prop({ type: Boolean, required: true })
  visible: boolean

  @Prop({ type: Number, default: 5000 })
  timeout: number

  @Watch('visible', { immediate: true })
  onVisibilityChange(visible, oldValue) {
    console.log(
      'AppSnackbar -> onVisibilityChange -> newValue',
      visible,
      oldValue
    )
    if (this.visiblityTimer) clearTimeout(this.visiblityTimer)

    if (visible) {
      this.visiblityTimer = setTimeout(() => {
        this.$emit('hide')
      }, this.timeout)
    }
  }
}
</script>

AppSnackbar.spec.js

it('should emit hide event after set timeout', async () => {
    const wrapper = shallowMount(Snackbar, {
      slots: {
        default: 'foo bar',
      },
      propsData: {
        visible: false,
        timeout: 1000,
      },
    })

    wrapper.setProps({ visible: true })
    await wrapper.vm.$nextTick()

    jest.advanceTimersByTime(1000)
    expect(wrapper.emitted().hide).toBeDefined()
  })

The outcome is:

1st console log from immediate
console.log packages/core/components/GlobalComponents/AppSnackbar.vue:30
    AppSnackbar -> (newValue, oldValue) -> false undefined

2nd console log from setProps
console.log packages/core/components/GlobalComponents/AppSnackbar.vue:30
    AppSnackbar -> (newValue, oldValue) -> true false

3rd console log ????????
console.log packages/core/components/GlobalComponents/AppSnackbar.vue:30
    AppSnackbar -> (newValue, oldValue) -> false true

There is no place changing the visible value to false and this is the only test inside the spec.

The version is: 1.0.0-beta.32

@JessicaSachs
Copy link
Collaborator

@JessicaSachs JessicaSachs commented Apr 1, 2020

@AtofStryker
Copy link
Contributor

@AtofStryker AtofStryker commented Jul 31, 2020

This likely related to #1419 and might be fixed by #1618?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
1.0
  
To do
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
5 participants
You can’t perform that action at this time.