How to mock window.location.href with Jest + Vuejs? - vue.js

Currently, I am implementing unit tests for my project and there is a file that contains window.location.href.
I want to mock this to test and here is my sample code:
it("method A should work correctly", () => {
const url = "http://dummy.com";
Object.defineProperty(window.location, "href", {
value: url,
writable: true
});
const data = {
id: "123",
name: null
};
window.location.href = url;
wrapper.vm.methodA(data);
expect(window.location.href).toEqual(url);
});
But I get this error:
TypeError: Cannot redefine property: href
at Function.defineProperty (<anonymous>)
How should I resolve it?

You can try:
global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, 'location', {
value: {
href: url
}
});
expect(window.location.href).toEqual(url);
Have a look at the Jest Issue for that problem:
Jest Issue

2020 Update
Basic
The URL object has a lot of the same functionality as the Location object. In other words, it includes properties such as pathname, search, hostname, etc. So for most cases, you can do the following:
delete window.location
window.location = new URL('https://www.example.com')
Advanced
You can also mock Location methods that you might need, which don't exist on the URL interface:
const location = new URL('https://www.example.com')
location.assign = jest.fn()
location.replace = jest.fn()
location.reload = jest.fn()
delete window.location
window.location = location

I have resolved this issue by adding writable: true and move it to beforeEach
Here is my sample code:
global.window = Object.create(window);
const url = "http://dummy.com";
Object.defineProperty(window, "location", {
value: {
href: url
},
writable: true
});

Solution for 2019 from GitHub:
delete global.window.location;
global.window = Object.create(window);
global.window.location = {
port: '123',
protocol: 'http:',
hostname: 'localhost',
};

The best is probably to create a new URL instance, so that it parses your string like location.href does, and so it updates all the properties of location like .hash, .search, .protocol etc.
it("method A should work correctly", () => {
const url = "http://dummy.com/";
Object.defineProperty(window, "location", {
value: new URL(url)
} );
window.location.href = url;
expect(window.location.href).toEqual(url);
window.location.href += "#bar"
expect(window.location.hash).toEqual("#bar");
});
https://repl.it/repls/VoluminousHauntingFunctions

Many of the examples provided doesn't mock the properties of the original Location object.
What I do is just replace Location object (window.location) by URL, because URL contains the same properties as Location object like "href", "search", "hash", "host".
Setters and Getters also work exactly like the Location object.
Example:
const realLocation = window.location;
describe('My test', () => {
afterEach(() => {
window.location = realLocation;
});
test('My test func', () => {
// #ts-ignore
delete window.location;
// #ts-ignore
window.location = new URL('http://google.com');
console.log(window.location.href);
// ...
});
});

Working example with #testing-library/react in 2020 for window.location.assign:
afterEach(cleanup)
beforeEach(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: { assign: jest.fn() }
})
})

Extending #jabacchetta's solution to avoid this setting bleeding into other tests:
describe("Example", () => {
let location;
beforeEach(() => {
const url = "https://example.com";
location = window.location;
const mockLocation = new URL(url);
mockLocation.replace = jest.fn();
delete window.location;
window.location = mockLocation;
});
afterEach(() => {
window.location = location;
});
});

How to reassign window.location in your code base; the simplest working setup we found for our Jest tests:
const realLocation = window.location;
beforeEach(() => {
delete window.location;
});
afterEach(() => {
window.location = realLocation;
});

you can try jest-location-mock.
npm install --save-dev jest-location-mock
update jest configs at jest.config.js file or jest prop inside package.json:
setupFilesAfterEnv: [ "./config/jest-setup.js" ]
create jest-setup.js
import "jest-location-mock";
usage:
it("should call assign with a relative url", () => {
window.location.assign("/relative-url");
expect(window.location).not.toBeAt("/");
expect(window.location).toBeAt("/relative-url");
});

You can try a helper:
const setURL = url => global.jsdom.reconfigure({url});
describe('Test current location', () => {
test('with GET parameter', () => {
setURL('https://test.com?foo=bar');
// ...your test here
});
});

This is valid for Jest + TypeScript + Next.js (in case you use useRoute().push
const oldWindowLocation = window.location;
beforeAll(() => {
delete window.location;
window.location = { ...oldWindowLocation, assign: jest.fn() };
});
afterAll(() => {
window.location = oldWindowLocation;
});

JSDOM Version
Another method, using JSDOM, which will provide window.location.href and all of the other properties of window.location, (e.g. window.location.search to get query string parameters).
import { JSDOM } from 'jsdom';
...
const { window } = new JSDOM('', {
url: 'https://localhost/?testParam=true'
});
delete global.window;
global.window = Object.create(window);

I could not find how to test that window.location.href has been set with correct value AND test that window.location.replace() has been called with right params, but I tried this and it seems perfect.
const mockWindowLocationReplace = jest.fn()
const mockWindowLocationHref = jest.fn()
const mockWindowLocation = {}
Object.defineProperties(mockWindowLocation, {
replace: {
value: mockWindowLocationReplace,
writable: false
},
href : {
set: mockWindowLocationHref
}
})
jest.spyOn(window, "location", "get").mockReturnValue(mockWindowLocation as Location)
describe("my test suite", () => {
// ...
expect(mockWindowLocationReplace).toHaveBeenCalledWith('foo')
expect(mockWindowLocationHref).toHaveBeenCalledWith('bar')
})

Can rewrite window.location by delete this global in every test.
delete global.window.location;
const href = 'http://localhost:3000';
global.window.location = { href };

Based on examples above and in other threads, here is a concrete example using jest that might help someone:
describe('Location tests', () => {
const originalLocation = window.location;
const mockWindowLocation = (newLocation) => {
delete window.location;
window.location = newLocation;
};
const setLocation = (path) =>
mockWindowLocation(
new URL(`https://example.com${path}`)
);
afterEach(() => {
// Restore window.location to not destroy other tests
mockWindowLocation(originalLocation);
});
it('should mock window.location successfully', () => {
setLocation('/private-path');
expect(window.location.href).toEqual(
`https://example.com/private-path`
);
});
});

Probably irrelevant. But for those seeking a solution for window.open('url', attribute) I applied this, with help of some comments above:
window = Object.create(window);
const url = 'https://www.9gag.com';
Object.defineProperty(window, 'open', { value: url });
expect(window.open).toEqual(url);

Here's a simple one you can use in a beforeEach or ala carte per test.
It utilizes the Javascript window.history and its pushState method to manipulate the URL.
window.history.pushState({}, 'Enter Page Title Here', '/test-page.html?query=value');

I use the following way using the Jest's mocking mechanism (jest.spyOn()) instead of directly overwriting the object property.
describe("...", () => {
beforeEach(() => {
const originalLocation = window.location;
jest.spyOn(window, "location", "get").mockImplementation(() => ({
...originalLocation,
href: "http://dummy.com", // Mock window.location.href here.
}))
});
afterEach(() => {
jest.restoreAllMocks()
});
it("...", () => {
// ...
})
});
I learned it from this post.

Related

SpeechSynthesis doesn't work on any browser but iOS Safari [Vue]

I wrote an component in Vue which allows to spell provided phrase on component click. Unfortunately, it works only on iOS Safari. Doesn't work on any other browser. When I console.log utterance it seems that it has all necessary options to make it work. I store informations about voice and SpeechSynthesis availability in Vuex. Everything works fine until synthesis.speak(...). Also event listeners (end) don't trigger. Assigning 1 to volume / rate / pitch also doesn't solve the problem.
What's wrong with my code?
const ON_SPEAK_END = "end";
export default defineComponent({
props: {
phrase: {
type: String,
required: true,
},
},
setup(props) {
const store = useStore();
const isSpeechAvailable = computed(() => store.getters.getSpeechAvailable);
const voice = computed(() => store.getters.getCurrentVoice);
const fullPhrase = toFullPhrase(props.phrase);
const utterance = new SpeechSynthesisUtterance();
const toggleSpeakStatus = () => {
// do sth
};
onMounted(() => {
utterance.text = fullPhrase;
utterance.lang = voice.value.lang;
utterance.voice = voice.value;
utterance.addEventListener(ON_SPEAK_END, toggleSpeakStatus);
});
onBeforeUnmount(() => {
utterance.removeEventListener(ON_SPEAK_END, toggleSpeakStatus);
});
const speak = () => {
if (!isSpeechAvailable.value) {
return;
}
const synthesis = window.speechSynthesis;
if (!synthesis.speaking) {
// code works until this moment:
synthesis.speak(utterance);
}
};
return { speak, voice };
},
});
</script>

how to mock window.eventBus.$on - Vue.js | Jest Framework

Need to test the emitted value for test case coverage.
window.eventBus.$on('filter-search-content', () => {
console.log('Yes it was emitted');
this.showFilter = true;
});
This what i have tried. But it's not worked out for me.
it('should all the elements rendered', () => {
global.eventBus = {
$on: jest.fn(),
}
// global.eventBus.$emit('filter-search-content'); --> This also not working
wrapper = mountAppointment(data);
wrapper.vm.eventBus.$emit('filter-search-content');
expect(wrapper.vm.showFilter).toBe(true);
});
Here is the example code we can follow.
emitEvent() {
this.$emit("myEvent", "name", "password")
}
Here is the test case
describe("Emitter", () => {
it("emits an event with two arguments", () => {
const wrapper = shallowMount(Emitter)
wrapper.vm.emitEvent()
console.log(wrapper.emitted())
})
})

How do I use #vue/test-utils to setProps and have them show up in the HTML?

I have the my code working in a sandbox and now I am trying to write the test. However, When I try this...
test("Hello World", async () => {
let list = [
{
name: "foo"
}
];
var data = {
list
};
const wrapper = mount(MyComponent, data);
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setProps({ list });
await wrapper.vm.$nextTick();
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});
However, expect(wrapper.html()).toContain("bar"); fails because it cannot fine the text. I can see it work using setTimeout so I am not sure what I am missing.
How do I see the prop changes in the html?
Your component is not expecting any props. When you mounting your component you are setting component's data property. And if you want to change it later in test after mounting you should call setData.
Also there is a mistake in your test: according to docs data must be a function.
With all being said your test should look like that:
test("Hello World", async () => {
const list = [
{
name: "foo"
}
];
const data = () => {
list
};
const wrapper = mount(MyComponent, {
data
});
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).not.toContain("bar");
list.push({
name: "bar"
});
await wrapper.setData({ list });
expect(wrapper.html()).toContain("foo");
expect(wrapper.html()).toContain("bar");
});

AddEventListener DOM event Jest testing

I have one Users vue component and I am trying to test mounted() with addEventListener.
Users.vue
=========
mounted(){
let viewPort = document.getElementById("Users-list"); ----> Here I am getting null for addEventListener.
viewPort!.addEventListener("scroll", (e: any) => {
let result =
e.target.scrollHeight - e.target.scrollTop - e.target.clientHeight ===
0;
if (result) {
this.test = this.Offset + this.Limit;
this.response = this.GetDetails();
}
});
}
I have written spec for Users component and trying to test mounted() method with addEventListener.
But I am getting an error message cannot read property addEventListener of null.
Users.spec.ts
=============
describe('Users TestSuite', async () => {
let userWrapper: any;
let userObj: any;
beforeEach(() => {
userWrapper = shallowMount(Users, {
// attachTo: document.getElementById('Users-list'),
localVue,
i18n,
router
})
userObj = userWrapper.findComponent(Users).vm;
const mockAddeventListener = jest.fn().mockImplementation((event, fn) => {
fn();
})
document.getElementById = jest.fn().mockReturnValue({
scrollTop: 100,
clientHeight: 200,
scrollHeight: 500,
addEventListener: mockAddeventListener
})
expect(mockAddeventListener).toBeCalledWith('scroll', expect.anything());
});
it('should render Users page', () => {
expect(userObj).toBeTruthy();
});
I think the problem here might be u are creating the mock function after u are creating the component. Mounted method will be called when the wrapper is created so try to move the mock implementation above the wrapper statement.
Another sure way in which to make it work is before u create the wrapper set the body of the document like document.body.innerHTML = <div id="users-list"></div>. This will definitely work.
For both the above solutions make sure that they are above the wrapper statement.

How to fix NativeModule.RNLocalize is null?

I am using react-native-localization library is my RN project.
My RN version is 0.59.4
I already get the project to work on android as expected, but the problem is with the IOS build.
I npm installed both react-native-localization and react-native-localize and linked them as described in their github manual using pod.
I did everything I could from linking to clean and building the project multiple times.
But I'm getting this error when running react-native-localize NativeModule.RNLocalize is null. To fix this issue try these steps and I did what the console told me but IN VAIN.
Can someone please tell me what I'm doing wrong?
Create a mock file like this (in the root directory):
__mocks__/react-native-localize.js
Check that __mock__ has two underscores.
This is an example of the file:
const getLocales = () => [
// you can choose / add the locales you want
{ countryCode: "US", languageTag: "en-US", languageCode: "en", isRTL: false },
{ countryCode: "FR", languageTag: "fr-FR", languageCode: "fr", isRTL: false },
];
// use a provided translation, or return undefined to test your fallback
const findBestAvailableLanguage = () => ({
languageTag: "en-US",
isRTL: false,
});
const getNumberFormatSettings = () => ({
decimalSeparator: ".",
groupingSeparator: ",",
});
const getCalendar = () => "gregorian"; // or "japanese", "buddhist"
const getCountry = () => "US"; // the country code you want
const getCurrencies = () => ["USD", "EUR"]; // can be empty array
const getTemperatureUnit = () => "celsius"; // or "fahrenheit"
const getTimeZone = () => "Europe/Paris"; // the timezone you want
const uses24HourClock = () => true;
const usesMetricSystem = () => true;
const addEventListener = jest.fn();
const removeEventListener = jest.fn();
export {
findBestAvailableLanguage,
getLocales,
getNumberFormatSettings,
getCalendar,
getCountry,
getCurrencies,
getTemperatureUnit,
getTimeZone,
uses24HourClock,
usesMetricSystem,
addEventListener,
removeEventListener,
};
You don't have to import the node_module of react-native-localization because each file under __mocks__ will be automatically mocked.
Try to run the test again and check if the error persist.
Edit: in my case, the only function that I needed from react-native-localize was uses24HourClock() so my mock file was very short:
const uses24HourClock = () => false;
export { uses24HourClock };
That was all for me.