I've got a vue application which exists of a page component and a nested child component, which makes a async post-request. If the request is failing, an error is thrown, which is propagated to the top component (Page.vue). This top component handles all errors of its child with the errorCaptured-Lifecycle hook. Here is an working example on stackblitz
The mechanism is working fine and no warning is displayed for my app. However, I want to test my child component isolated with jest and vue-test-utils. Therefore, I made a test:
import { mount } from "#vue/test-utils";
import Child from "./Child";
jest.mock("./api", () => ({
createSomething: jest.fn(),
}));
it("creation is erroneous", async () => {
const wrapper = mount(Child);
createSomething.mockImplementation(() => {
throw new Error("error");
});
let createAction = wrapper.find("[data-test-create]");
await createAction.trigger("click");
});
However, because the error is rethrown and never catched by the parent component, there is always a console log statement in my test output:
console.warn
[Vue warn]: Unhandled error during execution of native event handler
at <Child ref="VTU_COMPONENT" >
at <VTUROOT>
How can i solve this? I tried to wrap await createAction.trigger("click"); with try/catch but its not working. Do I need to mock the errorCaptured lifecycle of my parent?
One solution is to set a no-op app.config.errorHandler via the global.config mounting option:
const wrapper = mount(Child, {
global: {
config: {
errorHandler(err) { /* ignore */ },
},
},
})
demo
Related
I am using for the first time the aurelia-testing package to test my HTML code.
I seem to have followed the docs to set up my test as follows:
describe('Contextual Menu HTML View test suite', function () {
let component: ComponentTester;
beforeEach(function () {
component = StageComponent
.withResources('../../src/components/modal/contextual-menu')
.inView('<contextual-menu is-active.two-way="activateMenu"></contextual-menu>')
.boundTo({ activateMenu: false });
});
it('should not add the is-active class to the root element', async function () {
await component.create(bootstrap);
const rootElement = await waitForDocumentElement('.qa-contextual-menu');
expect(rootElement.classList.contains('is-active')).toBe(false);
});
afterEach(function () {
component.dispose();
});
});
I tried using just bind instead of two-way but that fails too.
I tried both with a document.querySelector and with waitForDocumentElement, both cases fail, but anyways I assume the error comes from earlier.
I am getting an error and I am not sure why. Could you put on the tracks to identify the root cause of the following:
TypeError: Failed to execute 'replaceChild' on 'Node': parameter 1 is not of type 'Node'.
at Object.convert (/Users/lemoustachiste/work/lm-frontend/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/Node.js:573:11)
at HTMLDivElement.replaceChild (/Users/lemoustachiste/work/lm-frontend/node_modules/jest-environment-jsdom/node_modules/jsdom/lib/jsdom/living/generated/Node.js:292:31)
at NodeJsDom.replaceNode (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-pal-nodejs/dist/nodejs-dom.js:95:29)
at makeElementIntoAnchor (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2432:19)
at applyInstructions (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2479:17)
at ViewFactory.create (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:2707:7)
at TemplatingEngine.enhance (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-templating/dist/commonjs/aurelia-templating.js:5290:24)
at /Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-framework/dist/commonjs/aurelia-framework.js:176:28
at new Promise (<anonymous>)
at Aurelia.enhance (/Users/lemoustachiste/work/lm-frontend/node_modules/aurelia-framework/dist/commonjs/aurelia-framework.js:174:12)
Thanks a lot
I am not a jest user. So I simply used the scaffolded TS jest skeleton app from aurelia-cli (au new jest-skeleton --unattended --select typescript,jest,vscode). And found that to be working.
It seems the you are missing the following configuration in your jest.config.js.
testEnvironment: "node",
After only adding that, the tests started working.
I'm getting the following warning when an API request returns 401 and I send the user to the login screen:
Warning: Can't perform a React state update on an unmounted component.
What is the best way to handle this warning in a functional component that uses hooks. See the code below:
.
.
export default function MovieDetailsScreen() {
const [movie, setMovie] = useState({});
const movieId = useNavigationParam('movieId');
useEffect(() => {
// This is the method that does the request and returns 401 (It
// uses the fetch library)
Client.movies.show(movieId)
.then(result => {
setMovie(result)
})
}, [])
.
.
.
In general, warnings don't crash your application. But you should care about them. For instance, the previous warning(s) can lead to performance issues when not properly unmounting your stateful components
the request (e.g. Promise) isn't resolved yet, but you unmount the component. Then the request resolves, setMovie() is called to set the new state, but it hits an unmounted component.
You can try catch
Client.movies.show(movieId)
.then(result => {
setMovie(result)
})
.catch((err) => {
Client.movies.stop()
})
I am using Vue (server side rendered) with mjml to generate emails.
So I have something (overly simplified) like:
<mjml><mj-body>Hello {{ User.Name }}</mj-body></mjml>
If the model doesn't define User then Vue throws an error and the whole output is lost.
What I want to the output to be along the lines:
<mjml><mj-body>Hello <error>'User' is undefined</error></mj-body></mjml>
I have implemented Vue.config.errorHandler but that just tells me about the error -- there is no rendered output.
Anyway to implement the equivalent of an error handler around each variable substitution?
If you are using Vue version >= 2.5, you can use errorCaptured to create an ErrorBoundary
const ErrorBoundary = {
name: 'ErrorBoundary',
data: () => ({
error: false,
msg: ''
}),
errorCaptured (err, vm, info) {
this.error = true
this.msg = `${err.stack}\n\nfound in ${info} of component`
},
render (h) {
return this.error
? h('pre', this.msg)
: this.$slots.default[0]
}
}
and use this in your component
<error-boundary>
<mjml><mj-body>Hello {{ User.Name }}</mj-body></mjml>
</error-boundary>
If the application has any javascript error, it will be displayed on UI
Example on codepen
If you want to have more user-friendly error, you can customize ErrorBoundary to have fallback to another component. You can find out in this tutorial
Another good example of using errorHandler
I need to use $nextTick in order to test some parts of my program. Somehow, it breaks my tests and make them success all the time - even when they should fail.
A minimal test sample would look like this:
import App from "./App";
import { shallowMount } from "#vue/test-utils";
it("should fail", () => {
const wrapper = shallowMount(App);
wrapper.vm.$nextTick(() => {
expect(1).toBe(3);
done();
});
});
You can find a sandbox example here
If you open the console, you should find the following error messages:
[Vue warn]: Error in nextTick: "Error: expect(received).toBe(expected)
Error: expect(received).toBe(expected)
Why does the test success? Why are the errors ignored? How do I use $nextTick properly if note like so?
In order to wait until Vue.js has finished updating the DOM after a
data change, you can use Vue.nextTick(callback) immediately after the
data is changed. The callback will be called after the DOM has been
updated.
I can not see any trigger that change DOM in your test. And you missed done argument in test callback
For example in the following this is wrapper.find('button').trigger('click')
it('fetches async when a button is clicked', (done) => {
const wrapper = shallowMount(Foo)
wrapper.find('button').trigger('click')
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.value).toBe('value')
done()
})
})
I would like to use JS Hook as described here. Specially, I want to use the afterEnter hook with an async component.
This is my async component:
Vue.component('example', function(resolve, reject){
let data = {
text: 'test data',
};
$.post('http://example.com', data, function(r){
r = JSON.parse(r);
if( r.success ) {
resolve({
template: r.data,
afterEnter: function(el, done){
console.log('test');
}
});
}
});
});
This is what the ajax call gets from the server, and it's what is passed to the template in r.data.
<transition v-on:after-enter="afterEnter"></transition>
These are the two errors that I get.
[Vue warn]: Property or method "afterEnter" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
[Vue warn]: Invalid handler for event "after-enter": got undefined
Is it possible to use JS hooks with async components? And if not, how should I approach this? My objective is to run custom JS after Vue (and/or vue-router) inject the component template onto the page, so that I can initiliaze image sliders and whatnot. It is important that my custom JS fires every time the component is navigated to, and not only on the first load.
Thank you.
That warning means that Vue is looking for (but unable to find) a property or method named "afterEnter", which you reference in your template. You have defined afterEnter in your resolve function as if it is a lifecycle hook, but it needs to be one of your Vue instance's methods.
So, your resolve function should look like this:
resolve({
template: r.data,
methods: {
afterEnter: function(el, done) {
console.log('test');
}
}
});