Shadow DOM and testing it via Jasmine - karma-jasmine

I have a webcomponent that creates a shadow DOM and adds some html to its shadowRoot.
class SomeThing extends HTMLElement {
attachedCallback () {
this.el = this.createShadowRoot();
this.render();
}
render () {
this.el.innerHTML = '<h1>Hello</h1>';
}
}
export default SomeThing;
And I am compiling it with the help of webpack and its babel-core and babel-preset-es2015 plugins.
Also I am using Karma and Jasmine to write my Unit Test. This is what it looks like.
describe('some-thing', function () {
var someElement;
beforeEach(function () {
someElement = document.createElement('some-thing');
});
it('created element should match string representation', function () {
var expectedEl = '<some-thing></some-thing>';
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.innerHTML).toBe(expectedEl);
});
it('created element should have shadow root', function () {
var wrapper = document.createElement('div');
wrapper.appendChild(someElement);
expect(wrapper.querySelector('some-thing').shadowRoot).not.toBeNull();
})
});
I want to see if there is something in the shadowRoot of my element, and want to write test cases for the HTML and events created inside the shadowRoot. But the second test is failing. It is not able to add shadowRoot to the some-element DOM.
If anyone can help me out, that would be helpful.
I am also uploading the full test working project on Github. You can access it via this link https://github.com/prateekjadhwani/unit-tests-for-shadow-dom-webcomponents
Thanks in advance

I had a similar problem testing a web component but in my case I am using lit-element from polymer/lit-element. Lit-element provides life cycle hooks, template rendering using lit-html library (documentation).
So this is my problem and how I solved. I noticed that the component was added and the class executed constructor and I had access to public methods using:
const element = document.querySelector('my-component-name')
element.METHOD();
element.VARIABLE
But it never reached the hook firstUpdated, so I thought the problem was the speed the test executes vs the speed component is created. So I used the promised provided by lit-element API (updateComplete):
Note: I use mocha/chai instead of Jasmine
class MyComponent extends LitElement {
render() {
return html`<h1>Hello</h1>`
}
}
customElements.define('my-component', TodoApp);
let element;
describe('main', () => {
beforeEach(() => {
element = document.createElement("my-component");
document.querySelector('body').appendChild(element);
});
describe('test', () => {
it('Checks that header tag was added to shadowRoot', (done) => {
(async () => {
const res = await element.updateComplete;
const header = element.shadowRoot.querySelector('h1');
assert.notEqual(header, null);
done();
})();
});
});
});
So, my advice is create a promise and resolve it when the render function is executed, use the promise to sync the creation of the component with tests.
I am using this repository to test concepts
https://github.com/correju/polymer-playground

Related

Vitest LitElement events

I have a storybook project where i am using Vite and LitElement components.
To test the components i thought i would use the Vitest library.
But i can't really test my components, it is like if the components aren't initialized / mounted / working (but they work fine in the stories., so i think the problem is with the testing).
I have a breadcrumb component, which dispatches a custom event on the connectedCallback function. On my story i can listen to this event, so i know it is being dispatched.
But i can seem to test it.
What i have:
on the breadcrumb component, inside the connectedCallback function
this.dispatchEvent(new Event('abc-breadcrumb-connected'));
on my breadcrumb.test.ts file:
import type { IWindow } from 'happy-dom';
import { expect, describe, it, beforeEach, vi } from 'vitest';
import '../abc-breadcrumb';
import { AbcBreadcrumb } from "../abc-breadcrumb";
declare global {
interface Window extends IWindow {}
}
describe('Abc breadcrumb', async () => {
it('Dispatches connected event', async () => {
const mockConnectedCallback = vi.fn(() => true);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
mockConnectedCallback()
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockConnectedCallback).toHaveBeenCalled();
})
});
0n my vite.config.ts i have:
export default defineConfig({
test: {
globals: true,
environment: 'happy-dom',
},
...
})
the error i get:
AssertionError: expected "spy" to be called at least once
I have no idea why it isn't working an would be really happy to get some help.
Thanks!
At first look, using await new Promise(resolve => setTimeout(resolve, 0)); seems like the kind of thing I was unsuccessfully relying on myself to ensure enough ticks had passed for various operations to complete. This often worked in my local browser but failed in CI or resulted in flaky tests.
Why not set up the promise such that the event listener calls resolve() or possibly even mockConnectedCallback(). Then you can be certain the event isn't firing as opposed to only not having been fired when setTimeout resolves.
const mockConnectedCallback = vi.fn(() => true);
let connectedResolve;
const connectedPromise = new Promise(r => connectedResolve = r);
window.addEventListener('abc-breadcrumb-connected', () => {
console.log('GOT THE EVENT');
connectedResolve();
mockConnectedCallback();
});
document.body.innerHTML = `
<abc-breadcrumb role="nav" aria-label="Breadcrumb" class="breadcrumb" ismobile="">
...
</abc-breadcrumb>
`;
await window.happyDOM.whenAsyncComplete();
await connectedPromise;
expect(mockConnectedCallback).toHaveBeenCalled();
It makes the expect() a little redundant given that it won't be reached until the awaited promise resolves, but I think awaiting an explicit resolution makes things easier to reason about and doesn't hard-code various assumptions about microtask queues and things into the test code, which more often than not have come back to bite me later.

Provide Inject not working properly in vue 3 composition API

I am working with Vue 3 composition api and am retrieving weather data via async/await fetch and I get a 200 response and the data in the request within the Chrome Dev Tools.
In the component receiving the data and making the call I have a provide method and then I am injecting the data into another output component. The issue is in the inject component. The value for the injected variable is always null and does not update in the Vue Dev Tools so my data is never output to the screen. I went through the docs and the code is pretty much the same but I can't get it to work. Can anyone see an obvious issue?
Receiving Component
setup () {
async function getCurrentWeather () {
const response = await fetch(`${baseWeatherApiUrl}q=${userInput.value}`);
userInput.value = null;
return weatherData.value = await response.json();
}
const returnedWeatherData = reactive(weatherData);
provide('returnedWeatherData', returnedWeatherData);
return {
getCurrentWeather,
userInput,
weatherData
}
}
output component
setup () {
//Provide default of empty object in case no results exist
const weatherData = inject('returnedWeatherData');
console.log(weatherData) //No output even when making a new request to the weather api
return {
weatherData
}
}
As a separate test I tried to provide/inject hardcoded values found in the docs but still geolocation when injected remains null.
provide('geolocation', {
longitude: 90,
latitude: 135
})
const userGeolocation = inject('geolocation')
console.log(userGeolocation) // Nothing logged
return {
weatherData,
userGeolocation
}
In my case it was importing inject from "#vue/runtime-core" instead of "vue".
Of course provide was imported from "vue".
Just leaving here, maybe it's gonna save someone an hour.
The provide-ed argument should be the ref itself (not wrapped in a reactive()):
// Parent.vue
export default {
setup () {
const weatherData = ref()
// ❌
// const returnedWeatherData = reactive(weatherData);
// provide('returnedWeatherData', returnedWeatherData);
// ✅
provide('returnedWeatherData', weatherData);
}
}
And the child component's console.log() in setup() does not automatically get invoked again. You should wrap that call with watchEffect() so that it does get called upon change to the ref:
// Child.vue
import { inject, watchEffect } from 'vue'
export default {
setup () {
const weatherData = inject('returnedWeatherData')
// ❌
//console.log('new weatherData', weatherData.value)
// ✅
watchEffect(() => {
console.log('new weatherData', weatherData.value)
})
}
}
demo

How Test with Jest a function in the method "mounted" VueJS

I would to try call a function already mocked. I use vueJS for the frond and Jest as unit test. Below a example of my code. My purpose is to test the call of « anotherFunction". The first test is succeed , not the second.Thanks for help or suggestion
code vueJS:
mounted() {
this.myfunction();
}
methods: {
myfunction() {
this.anotherFunction();
}
}
Jest code:
describe('Home.vue', () => {
let wrapper = null;
const options = {
mocks: {
$t: () => 'some specific text',
},
methods: {
myFunction: jest.fn(),
},
};
it('Should renders Home Component', () => {
// Given
wrapper = shallowMount(Home, options);
// Then
expect(wrapper).toBeTruthy();
});
it('Should call anotherFunction', async (done) => {
// Given
wrapper.vm.anotherFunction = jest.fn().mockResolvedValue([]);
// When
await wrapper.vm.myFunction();
// THIS THE PROBLEM, myFunction is mocked and I can't call the function 'anotherFunction' inside...
// Then
// expect(wrapper.vm.anotherFunction).toHaveBeenCalled();
});
});
I was finding a good way to help you if this test case. So, I thought in something like the chuck code below:
import { mount } from '#vue/test-utils';
describe('Home', () => {
it('method calls test case', () => {
const anotherMethodMock = jest.fn();
wrapper = mount(Home, {
methods: {
anotherMethod: anotherMethodMock
}
});
expect(anotherMethodMock).toHaveBeenCalled();
});
});
But, the Jest threw the following exception:
[vue-test-utils]: overwriting methods via the methods property is deprecated and will be removed in the next major version. There is no clear migration path for themethods property - Vue does not support arbitrarily replacement of methods, nor should VTU. To stub a complex m ethod extract it from the component and test it in isolation. Otherwise, the suggestion is to rethink those tests.
I had the following insight, maybe, in this case, should be better to test the side effect of this anotherMethod calling. What does it change? Is something being shown to the user?
I believe that here we have started from the wrong concept.
I hope that this tip could be useful :)
As suggested by #Vinícius Alonso, We should avoid using methods and setMethods in our test cases because of it's deprecation. But you can still test the mounted lifecycle by mocking the functions that are being called during mount. So you can do something similar to below snippet.
describe('Mounted Lifecycle', () => {
const mockMethodOne = jest.spyOn(MyComponent.methods, 'methodOne');
const mockMethodTwo = jest.spyOn(MyComponent.methods, 'methodTwo');
it('Validate data and function call during mount', () => {
const wrapper = shallowMount(MyComponent);
expect(mockMethodOne).toHaveBeenCalled();
expect(mockMethodTwo).toHaveBeenCalled();
})
})
Do mount/shallowMount inside it only rather putting it outside of it as it was not working in my case. You can checkout more details on it if you want.

Testing with `Created` hook with `vue-test-utils` and `jest`

I have a Vue page like this:
<template>
</template>
<script>
created(){
this.doSomething();
}
methods: {
doSomething() {
.....
}
}
</script>
Now, we want to the testing of this created hook and check that doSomething() method is called.
Tried like this, jest is also imported in package.json
import {
shallowMount,
createLocalVue,
} from '#vue/test-utils';
const localVue = createLocalVue();
import Xyx from '/Xyx.vue';
const init = () => {
wrapper = shallowMount(Xyx, { localVue });
cmp = wrapper.vm;
};
describe('#created', () => {
it('#doSomething', () => {
init();
wrapper.setMethods({
doSomething: jest.fn(),
})
expect(cmp.doSomething).toHaveBeenCalled();
});
});
Can I do the unit test case of this created hook?
The methods option was deprecated in v1 of #vue/test-utils, so the accepted answer no longer works. I ran into this issue myself and decided to dig into the source to figure out how I could test this.
It looks like Vue actually stores all the hooks in the $options property. Each hook has an option that is an array of functions. It is important to note that a context is not bound to said functions, so you will need to use call or apply to execute them.
vm.$options.created.forEach(hook => {
hook.call(vm);
});
Because your method is called on created, it is run before you are setting the mock. Therefore, your test will fail.
You have to replace the method with the mock on initialization (in your case, on shallowMount):
describe('Xyz', () => {
it('should call doSomething() when created', () => {
const doSomething = jest.fn()
wrapper = shallowMount(Xyz, {
localvue,
methods: { doSomething }
});
expect(doSomething).toHaveBeenCalled();
});
});
Sidenote: you're not declaring cmp. At the start of your test, you should have a let cmp;
A very similar discussion here. Above the linked comment there's a method to mock properties of most Vue component lifecycle hooks.
It's possible to call hooks when we need in our tests. For example if we need to mock some data before calling a hook.
import App from '#/App.vue';
// in test
App.created.call(wrapper.vm);
Also in Typescript if we use vue-property-decorator it changes the shape of component, so needs to be done like this:
App.extendOptions.created.call(wrapper.vm)

Testing a function called on an object with Jest in React Native

EDIT
Current example,
it('CALLED THE canOpenURL FUNCTION', () => {
const wrapper = mount(<ResourceCardComponent {...mockProps} />);
const canOpenURLSpy = jest.spyOn(Linking, 'canOpenURL');
wrapper.find('TouchableOpacity').simulate('click');
expect(canOpenURLSpy).toHaveBeenCalled();
canOpenURLSpy.mockReset();
canOpenURLSpy.mockRestore();
});
Error
expect(jest.fn()).toHaveBeenCalled() Expected mock function to have
been called.
Problem
I am using Jest & Enzyme to test a class made with React Native. This class has a function inside of it that when fired off uses the Linking library to call canOpenUrl and openUrl. I can simulate the click event on the mounted component but I am having trouble knowing how much of this I can actually test.
My goal is to check if Linking.canOpenUrl ever fires off.
Exmaple
The function inside the component looks like this,
onPressLink() {
console.log('HEY THIS FUNCTION FIRED WOOT WOOT');
Linking.canOpenURL(this.props.url).then((supported) => {
if (supported) {
Linking.openURL(this.props.url);
}
});
}
I can simulate this firing off like this,
describe('onPressLink has been called!', () => {
it('It clicks the mock function onPressLink!', (done) => {
const wrapper = mount(<MyComponent {...mockProps} />);
const onPressLink = jest.fn();
const a = new onPressLink();
wrapper.find('TouchableOpacity').first().simulate('click');
expect(onPressLink).toHaveBeenCalled();
done();
});
});
Now that does work, but my goal is to use something like this,
expect(Linking.canOpenUrl).toHaveBeenCalled();
But I keep getting this error,
TypeError: Cannot read property '_isMockFunction' of undefined
Current code that is trying to check if this function is ever fired off. Which is inside the parent function that is clicked with the simulate method,
it('calls canOpenURL', () => {
const wrapper = mount(<MyComponent {...mockProps} />);
const canOpenURL = jest.spyOn(wrapper.instance, 'onPressLink');
wrapper.find('TouchableOpacity').simulate('click');
expect('Linking.canOpenUrl').toHaveBeenCalled();
});
Question
What is the proper way to check to see if Linking.canOpenURL is fired when its parent function is executed?
(Since Jest 19.0.0+)
You can spy on the Linking module methods using jest.spyOn().
(1) Tell jest to spy on the module method:
const spy = jest.spyOn(Linking, 'canOpenURL');
(2) After doing everything you need to test it, check the spy:
expect(spy).toHaveBeenCalled();
(3) Clean up and stop spying on the module method
spy.mockReset();
spy.mockRestore();
If you don't want the tests to use the actual implementation of the methods, you can fake them like this:
jest.spyOn(Linking, 'canOpenURL').mockImplementation(() => Promise.resolve());
Where the function passed to mockImplementation will be whatever you want the method to do when called.
Ref https://facebook.github.io/jest/docs/en/jest-object.html#jestspyonobject-methodname
When using the actual implementation of your module method, which is asynchronous, the promise might not have been resolved by the time you tested it. You need to make sure any promise is resolved in your method implementation before making any assertions on it.
One way to deal with this is using async/await, like so:
it('...', async () => {
// Wait for promise to resolve before moving on
await wrapper.instance().onPressLink();
// make your assertions
expect(...);
});
Another option is using expect().resolves, available since Jest 20.0.0, where you wait for some promise in the argument to expect() to resolve with a value before making an assertion on that value.
expect(somePromiseThatEventuallyResolvesWithValue).resolves.toBe(Value);
I've done in simplest way:
Steps to spy:
Make spy object for original function using jest
Call original function with / without argument(s)
Assert the function which should be called with valid argument(s)
Reset mock
Restore mock
Here is the sample example
DefaultBrowser.ts which is actual class.
import { Linking } from 'react-native';
export const openDefaultBrowser = async url => {
if (await Linking.canOpenURL(url)) {
Linking.openURL(url);
}
};
DefaultBrowser.test.ts which is test case class.
import { openDefaultBrowser } from '../DefaultBrowser';
import { Linking } from 'react-native';
describe('openDefaultBrowser with validate and open url', () => {
it('validate url', async () => {
const spy = jest.spyOn(Linking, 'canOpenURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
it('open url', async () => {
const spy = jest.spyOn(Linking, 'openURL');
openDefaultBrowser('https://www.google.com');
expect(spy).toBeCalledWith('https://www.google.com');
spy.mockReset();
spy.mockRestore();
});
});
Hope this helps you.
it('open url', async () => {
jest.spyOn(Linking, 'canOpenURL')
const spy = jest.spyOn(Linking, 'openURL')
openURL(sitePath)
await waitFor(() => {
expect(spy).toHaveBeenCalledWith(sitePath)
})
spy.mockReset()
spy.mockRestore()
})