Update text field in vue test utils (jest) not working - javascript

Hi i am new in jest and unit testing. i want to ask how to set value text input using vue test utils.
shortly I have custom component for text input, its my code
<input
v-model="local_value"
#keyup.enter="submitToParent"
:class="input_class"
:id="id"
:disabled="is_disabled"
:maxlength="max_length"
:placeholder="placeholder"
:autocomplete="(is_autocomplete) ? 'on' : 'off'"
:name="id"
:ref="id"
/>
and it's my test
it("type something on field", async () => {
const wrapper = shallowMount(TextInput, {
propsData: {
id: "my_input",
}
})
// find component (its work properly) and I want to try inserting some text
const input = wrapper.findComponent({ref: "my_input"})
input.element.value = "sample text"
input.setValue("sample text")
// the value still empty string (""), idk what happens with my code
console.log(wrapper.vm.local_value)
expect(wrapper.vm.local_value).toBe("sample text")
please tell me if you known solution for this problem, thank you for your time

As far as I know, setValue is async, so you might need to set it with
await input.setValue('sample text')

If you're setting the vm's local_value value, you'd want to set it as:
const wrapper = shallowMount(TextInput)
await wrapper.setData({
local_value: 'sample text'
})
.... if the local_value is declared in data(), or:
const wrapper = shallowMount(TextInput)
await wrapper.setProps({
local_value: 'sample text'
})
... if the local_value is declared in props() in your component.
Then check it with:
expect(wrapper.vm.local_value).toBe('sample text')
Although that's just the vm value and not rendered value on DOM.
If you'd like to check including the rendered value, you have to declare first the property value on your component for it to be testable:
<input
v-model="local_value"
// ... rest of the properties
:value="local_value"
/>
and test it with:
const input = await wrapper.findComponent({ ref: "my_input" })
expect(input.attributes('value').toBe('sample text')
Ideally, your test will look like this:
it('should have input value' async () => {
const wrapper = shallowMount(TextInput)
await wrapper.setProps({ local_value: 'sample text' })
const input = await wrapper.findComponent({ ref: 'my_input' })
expect(wrapper.vm.local_value).toBe('sample text')
expect(input.attributes('value').toBe('sample text')
})
👍 Some tip:
You can check the rendered attributes input by commenting all expect and put:
expect(input.attributes('').toBe('random chars here to force an error')
Console will display the errors with the expected attributes output

Related

How to write a Jest/Vitest unit test for an array filter method

I am new to React and learning Automated testing for the first time.
I have a very simple application, which essentially uses Dummy Data, and renders a table based on the data.
I also have a search input the user can use to filter the data.
I am trying to write a unit test for my function that filters the array based on the search input and then return the filtered array that is set in State.
My function is as follows,
const onClickHandler = () => {
// Filter state data
const filteredData = originalData.filter((entity) =>
entity.entityType.toLocaleLowerCase().includes(searchInput)
);
setFilterData(filteredData);
};
I am struggling to understand how I would write a unit test that covers this logic.
I have been able to write a test that covers the search input and the resetting of the input if the user clicks a Reset button.
Apologies, I hope I have explained this well enough.
First, you need to define what is a unit in your code, and the extract you pasted misses a lot of contexts.
I will assume:
the unit is a function
that function is "importable/visible" from the test scope (I'm missing the export in export const onClickHandler = ...)
originalData and searchInput are the parameters
So:
// file...
export const onClickHandler = (originalData, searchInput) => {
const filteredData = originalData.filter(({ entityType }) => (
entityType.toLocaleLowerCase().includes(searchInput))
);
setFilterData(filteredData);
};
For any test, I like the AAA approach:
// test...
import { onClickHandler } from 'path/to/file';
describe('path/to/file/and/file/name.ts', () => {
it('should filter values be searchInput', () => {
// ARRANGE
const searchInput = 'fo';
const originalData = [{ entityType: 'FOO' }, { entityType: 'BAR' }]
// ACT
const result = onClickHandler(originalData, searchInput);
// ASSERT
expect([result]).toEqual(originalData[0]);
}
};
If the function setFilterData is defined outside the tested unit, I use a mock instead of the original.
I hope that helps!
Cheers

In Enzyme, how to get a function from the props of a functional component?

I am writing unit tests for my react project using Jest and Enzyme.
As shown below, I passed a function named updateUser to the to-be-tested component EditCard via props.
describe('The EditCard screen', () => {
let wrapper;
beforeEach(() => {
const defaultProps: Partial<EditCardProps> = {
toggleEditing: jest.fn(),
user: mockUsers[0],
updateUser: jest.fn(), // passes this function to the "EditCard" component via props
showSnackbar: jest.fn(),
};
wrapper = shallow(<EditCard {...(defaultProps as EditCardProps)} />);
});
Then I want to test how many times it was called after simulating a click on a button.
it('should match the snapshot when the "Name" textfield is not filled and the "submit" button is clicked', () => {
wrapper.find('#Name').simulate('change', { target: { value: null } });
wrapper.find('#submit').simulate('click');
// Try to get the "updateUser" function from the props, but get "undefined".
expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
});
But I got the error shown below:
Matcher error: received value must be a mock or spy function
Received has value: undefined
24 | wrapper.find('#Name').simulate('change', { target: { value: null } });
25 | wrapper.find('#submit').simulate('click');
> 26 | expect(wrapper.prop('updateUser')).toHaveBeenCalledTimes(0);
Could someone tell me where I did wrong? Why I cannot get the function from the props and undefined was returned?
Thanks in advance!
A few tweaks to your code should have it working...
import * as React from "react";
import { mount, ReactWrapper } from "enzyme";
import EditCard from "../path/to/EditCard";
/*
I'd recommend defining jest fns here to make them easier to reference anywhere
within the tests below; otherwise, it'll have to referenced via
'defaultProps.updateUser', 'defaultProps.showSnackbar', ...etc.
Using 'const' here allows us to define these variables within the
module's closure -- in short, only accessible within these tests and NOT
globally accessible (from other file tests).
*/
const showSnackbar = jest.fn();
const toggleEditing = jest.fn();
const updateUser = jest.fn();
/*
if the EditCard component is properly typed, then you shouldn't need to
add types to this 'defaultProps' object
*/
const defaultProps = {
showSnackbar,
toggleEditing,
updateUser,
user: mockUsers[0]
};
describe('The EditCard screen', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
/*
I'd recommend mount over shallow because child components can be
deeply nested and require multiple .dive calls; however, if
you know the child components of "EditCard" are just simple JSX elements,
then shallow will be fine
*/
wrapper = mount(<EditCard {...defaultProps} />);
});
it("should not call 'updateUser' when the form is submitted with an empty '#Name' field", () => {
/*
I'm not sure what simulating "null" does for this input, but assuming this
input is required you should at least pass a string value -- assuming
"#Name" input is of type 'text' | 'password' | 'email' => string and
not a number. On a related note, if it's required, then simply remove this
code as it doesn't do much for the test.
*/
// wrapper.find('#Name').simulate('change', { target: { value: "" } });
/*
I'm assuming this then simulates a form submit. Unfortunately,
pressing the submit button won't work. Instead you'll have to
simulate a form submit. This is a limitation of Enzyme and last I
checked hasn't been/can't be fixed.
*/
wrapper.find('form').simulate('submit');
/*
it should then NOT call the jest.fn() "updateUser" when submitted since
'#Name' is empty. Notice that we're referencing 'updateUser' -- the jest fn
defined above -- and not the wrapper.prop fn.
*/
expect(updateUser).not.toHaveBeenCalled();
});
// include other tests...
});
Here's a working example (click the Tests tab to run tests):

Error using Chai and Jest to test `HTMLElement` DOM elements

Say I have a very simple component that takes in some props and renders a simple <div> for an invalid state:
// InvalidState.js
// Renders an "Invalid" state display
const render = (props = {}) => {
// Create the <div> element
let div = document.createElement('div');
div.classList.add('my-component');
div.classList.add('my-component--invalid');
div.innerHTML = props.message;
return div;
};
export { render };
I'm using Jest as a test runner and chai as an expectations/assertions library.
To test the above I tried;
// InvalidState.test.js
import { expect } from 'chai';
import * as InvalidState from './InvalidState';
let wrapper;
describe('<InvalidState />', () => {
it('renders the component', () => {
// Call to `render()` returns an HTMLElement
// (Specifcally, a HTMLDivElement)
wrapper = InvalidState.render({ message: 'some message' });
// Find an element by class name
const invalid = wrapper.getElementsByClassName('.my-component--invalid')[0];
// Test its contents
expect(invalid).to.have.text('some message');
});
});
However I get the error
expect(invalid).to.have.text(InvalidState.DEFAULT_MESSAGE);
^
Invalid Chai property: text. Did you mean "that"?
Are chai and jest the right libraries that I should use in testing HTML elements? Or is there a better alternative?
Why would the text() method above not execute correctly?
Thanks!
I discovered that text() only works with chai-jquery. Or at least that's what this cheatsheet says: https://devhints.io/chai
I revised it to use innerText and it worked fine
div.innerText = props.message;
expect(wrapper.innerText.eql(InvalidState.DEFAULT_MESSAGE);

Vue.js directive v-html not updating if the model is overwritten

By running the following code (a Vue.js component), I expect that, after the AJAX call returns, both the v-html directive and the console.log() display the same value.
On the contrary, v-html is stuck with "loading...(1)" even though obj.html has a different value, as console.log() confirms.
The behaviour is caused by getObject overwriting obj, and being afterwards obj.html undefined for a short time before getHTML returns (all this happens in function created).
Can please someone explain whether this is Vue's desired behavior (doc links are welcome), or whether should I submit a bug report, or finally whether I am simply structuring my code in a bad way?
Thanks in advance
<template>
<main v-html="obj.html || 'loading... (1)'">
</main>
</template>
<script>
export default {
name: 'Post',
data: function () {
return {
obj: {
html: 'loading... (2)'
}
}
},
created: async function () {
this.obj = await this.getObject()
this.obj.html = await this.getHtml()
console.log(this.obj.html)
},
methods: {
getObject: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0]
},
getHtml: async function () {
const resp = await this.$http.get('https://jsonplaceholder.typicode.com/todos')
return resp.body[0].title
},
}
}
</script>
The function getObject returns a String so at the first line of created hook
this.obj = await this.getObject()
you change the reference of the obj and you make it pointing to a string and then you try to put a property on a string, which does not work ;)
it's like you would do
this.obj = 'test'
then console.log(this.obj);
// test
and then this.obj.abc = 'whatever'
console.log(this.obj.abc);
// undefined
You would need to parse the object before, see JSON.parse(string)
Update:
If this is not the case i.e you somehow have an object coming from that service.
Then the only problem I can think is that you lose the reference of your original obj and v-html is still pointing to the old one. In that case you have to avoid modification of the root obj or you can use the vue $set method: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
It seems vue data objects are not deeply reactive, which means that altering a property will not trigger change detection in the template.
Try rearranging the created hook to compose the full object before assigning it to the data property. That way when the template reacts it will see the html property of obj.
Ref CodeSandbox
created: async function () {
const fetchedObj = await this.getObject()
fetchedObj.html = await this.getHtml()
this.obj = fetchedObj;
console.log(this.obj.html)
},

How to properly implement detectChanges() in Angular2?

I have the following code in jasmine:
it('should pass on writing secondvalue in the input', async(() => {
const fixture=TestBed.createComponent(AppComponent);
const app=fixture.debugElement.nativeElement.querySelector("input").getAttribute("value");
expect(app).toContain("firstvalue");
fixture.detectChanges();
expect(app).toContain("secondvalue");
}));
The problem is that as soon as I run the test, the test fails. I expect it to wait because of the detectChanges() but it doesn't.
How do I properly implement: Waiting for the second value input for the input and check if the value will be "secondvalue".
Shouldn't the fixture.detectChanges() act like a even-blocker, for instance that it waits for the input to be triggered when someone starts writing on it?
When you make a change to your component state, you run detectChanges so that the changes propagate.
For example,
pageTitle: string;
ngOnInit() {
this.pageTitle = 'first title';
}
And in the template:
<h4>{{pageTitle}}</h4>
In the test:
const fixture = TestBed.createComponent(AppComponent);
const h4 = fixture.debugElement.query(By.css('h4'));
console.log(component.pageTitle); // 'first title'
console.log(h4.nativeElement.textContent); // ''
fixture.detectChanges(); // Propagates ngOnInit changes
console.log(h4.nativeElement.textContent); // 'first title'
component.pageTitle = 'second title'; // Here we change state
console.log(component.pageTitle); // 'second title'
console.log(h4.nativeElement.textContent); // 'first title'
fixture.detectChanges(); // Propagate changes
console.log(h4.nativeElement.textContent); // 'second title'
A typical use case is checking things that depend on state, like having in template:
<div id="xxx" *ngIf="over18">Restricted content</div>
in component:
over18: boolean = false;
in test:
it('should show restricted content if over 18', () => {
component.over18 = true; // change state from the default one
fixture.detectChanges(); // propagate changes to view
// now we can actually test
const divElem = fixture.debugElement.query(By.css('div#xxx')); // would be null if not shown in DOM
expect(divElem).toBeTruthy();
});
Note that I'm testing component logic. Checking that if I type "asdf" into input its value is going to update is, in my opinion, out of unit testing scope- this functionality is provided by HTML standard/ Angular team.

Categories

Resources