In Cypress I am changing the viewport of my Veu3 app after I preform any other test and it gives the error: "(uncaught execption) TypeError a.value is null".
The minimum app that produces the error:
<script setup>
import { ref, onMounted } from 'vue'
const a = ref(null)
onMounted (() => {
function onWindowResize() {
window.addEventListener("resize", () => {
//a.value always needs to refresh when the window resizes to different content
a.value.textContent = 'Text displayed here will be decided on the window size'
})
}
onWindowResize()
})
</script>
<template>
<div>
<span ref="a" data-testid="test1">M</span>
<a> ..more</a>
</div>
</template>
With the following test. The interesting thing to note here is that if you don’t include the first test the Error is not there.
import Test from '../Test.vue'
describe('run 2 test', () => {
it('The error only appears if I run a test before I run the viewport change', () => {
cy.mount(Test)
cy.get('[data-testid="test1"]').get('a').should('contain', ' ..more')
})
it('The error originates on the first viewport changes it appears', () => {
cy.mount(Test)
//so it appears here
cy.viewport(550, 750)
let charCount = 0
cy.get('[data-testid="test1"]').then(($span) => {
charCount = String($span.text()).length;
})
cy.viewport(400, 400)
cy.get('[data-testid="test1"]').then(($span) => {
const newCharCount = String($span.text()).length;
expect(newCharCount).to.be.eq(charCount)
})
})
})
I found this anwser to the question: "Vue3 TypeError ref.value is null" and tried adding defineExpose()
yet it doesn’t change a thing. I tried re-declaring the const a = ref(null)
within the onMounted()
also no luck. Been searching but can’t find a solution any help would be appreciated since a lot of the test will revolve around viewport changes.
The error causes the rest of the application, which depends on the a.value to fail.
UPDATE
Apparently it is me who can’t see what is wrong. So, I would like to understand why a.value should not exist in unMounted() (even thought this is an example in de Vue docs). Also why it does work in de browser without any errors. And why does it work in Cypress is no tests precede the test?
I would really like to understand and find a way around this.
3
Answers
When I use a ref in React, it’s always good practice to check the
ref.value
before accessing/assigning it.So if I put a guard inside the resize listener, the error
goes away.
Was not sure if the logic of the test still holds, though, because it may be throwing away a resize event.
So I added a check for the text "resize" (which is set by the listener), and it passes.
The rest of your test doesn’t pass, which follows because now the resizer is functioning.
Also, there’s a warning in the dev console that the
resize
event is being deprecated and will be removed (chrome browser).It suggests
I think your test is the wrong way round, if the resize changes the text so
expect(newCharCount).to.be.eq(charCount)
would not pass, because now the char count is larger. Seea.value.textContent = ...
Did you make a typo there?
Adding a guard is the correct action to stop the
uncaught:exception
.Window resize works independently of the Vue app, the error is occuring after the component is unmounted.
Adding some debugging:
Console:
Shows ref
el
is pointing at the span when mounted (as expected).Technically in
onBeforeUnmount()
you should remove the listener and that will also get rid of theuncaught:exception
.Since resize listener is deprecated, change it to MutationObserver instead.
Alternatively, use useElementSize from @vueuse/core.
I’m not sure about the versions of cypress that you’re using or how you run it, but what I did to reproduce your problem was to use vue 3, and cypress 13, and run the test with this command
cypress run --component --headed --no-exit --browser chrome
. And the error that I found isTypeError: Cannot set properties of null (setting 'textContent')
.What happened
Assuming my way of reproducing is correct, I can assure you that the error that you found "
TypeError ref.value is null
" is actually happening on the first test, not the second test. Want some proof? try to use these codes:If you run with those codes, you’ll see clearly that the error happens on the first test:
Why
So why exactly did that happen? It seems like when the first test finishes, the component doesn’t get unmounted, even after the second test starts to run, want some proof? try to update the script setup to this:
Now run the test again, and on the console, you’ll see that the interval prints don’t stop even when it should be cleared on the
onBeforeUnmount
lifecycle hook and after the test is finished.That would only mean that the component persists and is still affected by the next test. In other words, when this line
cy.viewport(550, 750)
is executed, it doesn’t just affect the component mounting on the second test, but also the first one. This is just my hunch, but for some reason, the first component isn’t unmounted properly (the component has no more elements after the first test is done, but maybe some of it is still there and cypress somehow still affects it)So why is it like that? to be honest i’m not sure myself, but i’d say that error is a false alarm for the test, because that error happens when the test (the first one) is already executed, so in my opinion, you can just ignore it. If you feel uncomfortable with the error, you could just wrap the value assignment inside a try catch like what I show above.
Or maybe you can unmount the component by following this guide.
EDIT
Actually, after some tweaking, you simply need to remove the window resize event listener when unmounting: