Testing form reset on Vue with Jest - javascript

Testing a form built with Vue using Jest for unit tests. Among the elements I have a reset button (type=reset), which works fine and once clicked it removes all the values already introduced.
However, when unit testing, the button click doesn't seem to clear the values. I don't have a handler for the click, just using the default reset function of the form.
I've also tried using wrapper.emmited('reset'); to no avail, and wrapper.emmitedByOrder(); returns an empty array.
How do I test that the reset button is generated correctly and works as expected?
test('Assert Form Components', async () => {
const wrapper = mount(FormElement, {
propsData: {
message: sampleJSON.formJSON
}
})
let resetBtn = wrapper.find('.form-reset');
let requiredInput = wrapper.find('.required-input');
....
requiredInput.setValue('test');
expect(requiredInput.element).toHaveValue('test'); //This passes
await resetBtn.trigger('click');
expect(requiredInput.element).not.toHaveValue('test') //This fails
....

Apparently there were two things missing. First, as #AlexMA suggested, allowing for another tick to let the DOM settle. Second, I needed to attach the wrapper to the DOM. The final code look something like this:
test('Assert Form Components', async () => {
const wrapper = mount(FormElement, {
propsData: {
message: sampleJSON.formJSON
},
attachTo: document.body
})
let resetBtn = wrapper.find('.form-reset');
let requiredInput = wrapper.find('.required-input');
....
requiredInput.setValue('test');
expect(requiredInput.element).toHaveValue('test'); //This passes
await resetBtn.trigger('click');
await wrapper.vm.$nextTick()
expect(requiredInput.element).not.toHaveValue('test') //This works now!
....

Related

How do I correctly mock a debounce event when unit testing UI in jest?

I currently have a unit test that will mock the click of a button. I've recently added a debounce function around it:
import * as lodash from 'lodash';
//...bunch of code in between
const buttonChangerTrue= lodash.debounce(() => buttonChanger(true, row), 500);
This click of the button will cause a change in the UI which will basically change the icon color (assuming the end user doesn't rapidly click it).
For the sake of brevity, I've removed the excess code for the test
this.when.push({
beforeEachRender: () => {
count++;
jest.useFakeTimers();
if (count === 1) {
this.mockHttpRequests();
} else {
this.mockHttpRequestsReversed('buttonPressed');
}
},
describe: 'When an unsaved search is ran and an agent is pinned',
then: async () => {
test('Then a note should be created', async () => {
const button = tableBody.children[1].children[1].children[0].children[0];
const stickyNoteBeforePinning =
this.getById(document.body, 'notes-69fc105ad2c94edf16efb1f4de125c38093aefe9') as HTMLElement;
if (!button ) {
throw 'button not found';
}
await wait(() => {
fireEvent.click(button);
});
jest.runTimersToTime(1000);
const stickyNoteAfterPinning =
this.getById(document.body, 'notes-ec9c2a3041a18a4a7d8d8b4943292cb8aa92a2f5') as HTMLElement;
expect(stickyNoteBeforePinning).toHaveAttribute('class', 'lead material-icons text-light');
expect(stickyNoteBeforePinning).not.toBe(stickyNoteAfterPinning);
expect(stickyNoteAfterPinning).toHaveAttribute('class', 'lead material-icons text-pink'); // Fails here
});
},
});
Please let me know if you need more information but this click of a button does make an API call, I've mocked the call in the test as well. When I remove the debounce function and run it as normal -- it passes the test just fine. I've tried a number of things like jest.useFakeTimers(); in the beforeEachRender portion and calling jest.runTimersToTime(1000); right after it. I've also tried using jest.advanceTimersByTime(500); right after the click too. Nothing seems to be working.
Edit: I ended up removing the tests for now. I read that Jest does have a version that can take into account something like jest.useFakeTimers('modern'); and that will simulate the debouncing. Will report back if I can get results on that.
You need to use useFakeTimers() from sinon
Here its an example of use:
import * as sinon from 'sinon';
let clock;
beforeEach(() => {
clock = sinon.useFakeTimers();
});
afterEach(() => {
clock.restore();
});
test('debounce', () => {
// wait 1000ms
clock.tick(1000);
func() // Execute your function.
expect(func).toHaveBeenCalledTimes(1); // func called
});

Cypress intercept blocks the request when it's called several times in a test run

I've created some tests in Cypress to add and duplicate article in our Angular application. The code for test ArticlesTest.js
describe('Shop articles single station', () => {
const productManagementPage = new ProductManagementPage()
const shopArticlesPage = new ShopArticlesPage()
before(() => {
var credentials =
{
"username": "someusername#user.name",
"password": "strongPassword!"
}
cy.navigateToProductManagement(credentials)
})
beforeEach(() => {
productManagementPage.shopArticlesProgrammingButton().click()
shopArticlesPage.WaitUntilPageLoaded('/productmanagement/api/v1/articles/get', 'GetArticles')
})
it('Add article', () => {
var randomNumber = RandomDataGenerator.GenerateRandomInt(1000,4999)
var randomName = RandomDataGenerator.GenerateRandomString(20)
var randomPrice = RandomDataGenerator.GenerateRandomDecimal(1,99)
shopArticlesPage.newArticleButton().click()
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
shopArticlesPage.deleteButton().should('be.disabled')
shopArticlesPage.articlesList().should('not.exist')
shopArticlesPage.articleNumberTextBox().should('be.enabled')
shopArticlesPage.articleNumberTextBox().type(randomNumber)
shopArticlesPage.articleNameTextBox().type(randomName)
shopArticlesPage.articleUnitPriceTextBox().type(randomPrice)
shopArticlesPage.undoButton().should('be.enabled')
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('exist')
shopArticlesPage.articlesList().should('exist')
shopArticlesPage.saveButton().should('be.disabled')
shopArticlesPage.undoButton().should('be.disabled')
})
it('Duplicate article', () => {
var articleNumber = RandomDataGenerator.GenerateRandomInt(51,65)
var newArticleNumber = RandomDataGenerator.GenerateRandomInt(1000, 4999)
var newArticleName = RandomDataGenerator.GenerateRandomString(20)
shopArticlesPage.articlesList().selectFromList(articleNumber)
const articleUnitPrice = shopArticlesPage.articleUnitPriceTextBox().invoke('text')
const vatCodeValue = shopArticlesPage.vatCodeDropDown().invoke('text')
const cardCodeValue = shopArticlesPage.cardCodeDropDown().invoke('text')
shopArticlesPage.duplicateArticleButton().click()
shopArticlesPage.WaitUntilPageLoaded()
shopArticlesPage.articleNumberTextBox().type(newArticleNumber)
shopArticlesPage.articleNameTextBox().type(newArticleName)
shopArticlesPage.saveButton().click()
shopArticlesPage.newArticleButton().should('be.enabled')
})
WaitUntilPageLoaded() method code is:
WaitUntilPageLoaded(path, alias) {
return cy.waitForRequestToComplete(path, alias)
}
which, in turn, is custom Cypress command:
Cypress.Commands.add('waitForRequestToComplete', (path, alias) => {
cy.intercept('POST', path).as(alias)
cy.wait('#' + alias).its('response.statusCode').should('be.ok')
})
In 1st beforeEach() run, there's no problem with intercepting GetArticles and waiting for it to complete.
The problem starts in 2nd test, as it looks like GetArticles is not intercepted, it's not called at all, though it's supposed to be. The problem doesn't exist when clicking through the application manually, and /articles/get is always invoked.
The test ends up with error message
Timed out retrying after 30000ms: cy.wait() timed out waiting 30000ms for the 1st request to the route: GetArticles. No request ever occurred.
I've also tried using other endpoint e.g. vatcodes/get, and it works perfectly. The problem occurs only for articles/get, but I don't see any trail that would tell my why this happens for articles endpoint.
What is the problem? Why Cypress "blocks" 2nd call to this endpoint? What's more interesting, the problem doesn't exist for GetFeatures alias, which is created in an identical way.
Make sure the network intercept is registered before the application makes the call.
it('is registered too late', () => {
cy.intercept('/todos').as('todos')
cy.visit('/')
cy.wait('#todos')
})
In our case, we need to register the intercept before visiting the page. Once the page is loaded, the application fetches the todo items, and everything is working as expected.
you can see this link: https://glebbahmutov.com/blog/cypress-intercept-problems/
If I'm reading the situation correctly, the last log image is the failing test.
There is no (xhr) 200 /productmanagement/api/v1/articles/get showing there.
It goes straight from api/v1/subscriptionfeatures/get to api/v1/vatcodes/get, but in the first test the api/v1/articles/get was between those two calls.
If it occurs later in the screenshot, add an increased timeout to catch it (the same intercept can use the longer timeout in both tests, but it won't delay the first test).
This may mean you have found a bug in the app - it seems that a "Duplicate" action should have the same POSTs as an "Add" action.
Have you resolved this?
I'm using this config:
Given('a GraphQL service error is thrown', () => {
cy.intercept({ method: 'POST', url: '/uat/graphql', times: 1 }, { forceNetworkError: true });
});
With times: 1. But the interception does not block the request now.
I found times in the docs.

Bootstrap V5 Modal not hiding via Javascript Unless Window Method

I'm currently developing a react app and using normal bootstrap.The command to show a modal and toggle to one works fine; however the hide command doesn't hide the modal unless I make it a property on window.
For example:
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = new bootstrap.Modal(currElement);
const secondModal = new bootstrap.Modal(element);
setLoading(false);
// #ts-expect-error
window.modal.hide(); // works fine
// second.modal.hide() doesn't work
new bootstrap.Modal(element).show();
resetForm();
}
}
However, I notice that on Chrome dev tools the _isShown is changing correctly to false
I was able to figure out a fix. The solution for anyone encountering this in the future is to not use the 'new bootstrap.Modal()' constructor syntax but to use the getInstance method on the modal.
Changing my code to the below caused it to work completely fine and without the use for creating a function on the window.
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = bootstrap.Modal.getInstance(currElement);
setLoading(false);
currentModal?.hide()
bootstrap.Modal.getInstance(element)?.show();
resetForm();
}
}

Vue test utils how to change select value with v-model

Hello I would like to check what happens after changing the value of select inside of the test.I'm using vue-test-utils.
Currently I'm unable to check if the select emitted the input event. I'm using Buefy select component if that matters.
The html part of component
<b-select
v-model="workHoursType"
>
<option :value="WORKING_HOURS_PERIOD.daily">{{daily}}</option>
<option :value="WORKING_HOURS_PERIOD.weekly">{{weekly}}</option>
</b-select>
Test, which is failing due to undefined is emitted
it("once working hours type is set to daily, the hours per week should have no value", async () => {
const wrapper = createWrapper();
const options = wrapper.find("select").findAll("option");
await options.at(1).setSelected();
await flushPromises();
expect(wrapper.emitted().input).toBeDefined();
});
I've seen also different approach for v-model based components however it still doesnt work for me.
it("with v-model", async () => {
const wrapper = createWrapper();
const select = wrapper.find("select");
const option = wrapper.findAll("option").at(0);
option.element.selected = true;
select.trigger("change");
await flushPromises();
expect(wrapper.emitted().input).toBeDefined();
});

Javascript DOM element .click() doesn't work in addEventListener? (discord)

I threw together a quick and dirty script for discord to quickly 'react' to a message by calling .click on the div. I open the console using ctrl+shift+i and enter the code below.
let chat;
chat = document.getElementsByClassName('da-scrollerInner')[0];
chat.addEventListener('DOMSubtreeModified', () => {
let msg = chat.children[chat.childElementCount - 2];
let want = false;
// determine if I should react or not based on message contents
if (want) {
let reactions = msg.getElementsByClassName('da-reactionInner');
let reaction = reactions[0];
if (reaction) {
reaction.click();
}
}
}
If I store reaction as a global variable and manually call reaction.click() it works and the reaction goes through. It also worked when I tried using setInterval to continuously check for a global variable and click it once it's defined. This works but neither solution is ideal.
let globalReaction;
// DOMSubtreeModified fired, globalReaction is set
let spam;
spam = setInterval(() => {
if (globalReaction) {
globalReaction.click();
}
}, 50);
If there is something I am missing please let me know. Thanks.

Categories